MQL5 EAの基本イベント構造を実コードで解説|OnInit・OnTick・OnDeinitの責務分離
MQL5でEAを作る時、最初に理解しておきたいのがOnInit・OnTick・OnDeinitの役割です。
EAは、チャートへセットされた時、価格が動いた時、削除・再初期化された時で呼ばれるイベント関数が異なります。このイベント構造を理解せずに処理を詰め込むと、初期化失敗、tickごとの処理過多、終了時のcleanup不安定、ログ不足などが起きやすくなります。
この記事では、MQL5 EAの基本イベント構造を、学習用に一般化したコード断片で解説します。完成EAソース全文、外部シート制御、認証、endpoint、token、URL、実運用set値、中核エントリー条件は公開しません。
扱うのは、OnInitで準備すること、OnTickで実行すること、OnDeinitで最小限に片付けること、そしてそれぞれをログで追えるようにする設計です。
この記事で扱う範囲
| 項目 | この記事で扱う内容 | 扱わない内容 |
|---|---|---|
| 目的 | EAの基本イベント構造と責務分離を理解する | 利益を狙う売買ロジック、具体的なエントリー条件 |
| 主な関数 | OnInit、OnTick、OnDeinit | 完成EAソース全文、実運用コード全文 |
| 設計対象 | 初期化、tick処理、終了処理、ログ、handle解放 | 外部制御、認証、実運用set値 |
| コード例 | 学習用に簡略化した短いコード断片 | 中核エントリー条件、独自優位性のある判定式 |
| 確認対象 | INITログ、TICK処理、DEINITログ、責務分離 | 推奨ロット、推奨銘柄、売買判断 |
OnInit・OnTick・OnDeinitの役割
MQL5 EAでは、処理の入口がイベント関数として分かれています。
| イベント関数 | 主なタイミング | 主な役割 |
|---|---|---|
| OnInit | EAをチャートへセットした時、再初期化時 | 初期化、handle作成、設定チェック、起動ログ |
| OnTick | 価格更新があった時 | 現在状態の取得、判定、見送り、発注候補、管理処理 |
| OnDeinit | EA削除、時間足変更、チャート変更、再コンパイル時など | timer停止、handle解放、軽量な終了処理 |
重要なのは、すべての処理をOnTickへ詰め込まないことです。OnInitで準備できるものは起動時に行い、OnTickはできるだけ軽くし、OnDeinitは安全に終了できる最小処理へ寄せます。
OnInitで行うこと
OnInitは、EAをチャートへセットした時に呼ばれる初期化処理です。
OnInitで行う代表的な処理は次の通りです。
- EA名、バージョン、symbol、timeframeの確認
- input値の簡易validation
- インジケーターハンドルの作成
- timerを使う場合のEventSetTimer
- チャートObjectのstartup audit
- ログモードや表示モードの初期化
- 初期化成功または安全停止のログ出力
一方で、OnInitに重い売買判定や発注処理を入れるべきではありません。OnInitはあくまで準備と検査の入口です。
OnTickで行うこと
OnTickは、価格が更新されるたびに呼ばれる処理です。
OnTickでは、tickごとに必要な処理だけを行います。代表的には、現在価格の確認、ポジション状態の取得、必要なインジケーター値の取得、entry guard、managed exit、signal評価などです。
ただし、OnTickは呼び出し回数が多いため、次のような処理を毎回行うと重くなります。
- 毎tickでインジケーターハンドルを作成する
- 毎tickで全Objectを走査する
- 毎tickで全Objectを削除・再作成する
- 毎tickで同じログを大量に出す
- 毎tickで外部通信や重い集計を行う
OnTickでは、処理をhelperへ分け、ログも状態変化時や失敗時に絞ると確認しやすくなります。
OnDeinitで行うこと
OnDeinitは、EAが終了または再初期化される時に呼ばれます。
OnDeinitでは、できるだけ軽い処理に限定します。
- Commentを消す
- timerを止める
- 作成済みインジケーターハンドルをIndicatorReleaseする
- 自EA所有が明確な軽量ステータスObjectを整理する
- 終了ログを出す
OnDeinitで広範囲のObject cleanupを行うと、EA削除時のフリーズ、Abnormal termination、手動ラインや他EAのObject巻き込みにつながる場合があります。チャートObjectの管理は、終了時に全部消すより、再セット時にstartup auditで監査する設計が安全な場面があります。
イベント関数へ処理を詰め込まない理由
OnInit・OnTick・OnDeinitはEAの入口ですが、そこへ全処理を直接書くと保守が難しくなります。
| 悪い例 | 問題点 | 改善方針 |
|---|---|---|
| OnTickに判定・発注・決済・ログ・Object更新をすべて書く | どこで止まったか追えない | Resolve / Evaluate / Executeへ分ける |
| OnInitに重い処理を入れる | 起動失敗や再初期化時の原因が追いにくい | validationと準備に限定する |
| OnDeinitで全Object削除する | 削除時の不安定化や他Object巻き込みが起きる | OnDeinitは最小化し、startup auditへ回す |
| ログを各所に直接Printする | 表記揺れ、ログ過多、検索しにくさが出る | ログhelperへ集約する |
| 値取得失敗とシグナル不成立を混同する | EAが動かない理由を切り分けにくい | 取得失敗、判定不成立、執行見送りを分ける |
イベント関数は、全処理を書く場所ではなく、必要なhelperを呼び出す入口として設計すると安定します。
基本構造の全体像
EAのイベント構造は、次のように分けると整理しやすくなります。
| 層 | 役割 | 例 |
|---|---|---|
| Init層 | 初期化、validation、handle作成 | InitRuntime、CreateHandles、InitSelfCheck |
| Market層 | 価格、symbol、spread、position状態の取得 | BuildMarketSnapshot、BuildPositionSnapshot |
| Signal層 | 判定候補の作成 | EvaluateSignal、BuildSignalSnapshot |
| Guard層 | 実行前の安全確認 | CheckSpread、CheckDuplicate、CheckRisk |
| Execution層 | 発注・決済・modify | SendOrder、ClosePosition、ModifyStop |
| Exit層 | 建値、トレール、保護決済 | RunManagedExit、RunSignalExit |
| Deinit層 | handle解放、timer停止、軽量終了 | ReleaseHandles、StopTimers |
この記事では売買条件の中身は扱いません。重要なのは、処理の入口と責務を分け、ログで確認できる形にすることです。
実コード断片1:OnInitで初期化を分ける
以下は、OnInitの中へ処理を詰め込まず、初期化helperへ分ける学習用コード例です。完成EAのソース全文ではありません。
// 学習用に一般化したOnInit構造例です。
// 実運用EAの完成ソースではありません。
bool g_runtimeReady = false;
int g_maHandle = INVALID_HANDLE;
bool InitRuntime()
{
g_runtimeReady = false;
Print("DIAG/INIT: event=START",
" symbol=", _Symbol,
" timeframe=", EnumToString(_Period));
return true;
}
bool ValidateInputs()
{
// 実際はinput値の範囲、ON/OFF整合、危険設定などを確認します。
Print("DIAG/INIT: event=VALIDATION_OK");
return true;
}
bool CreateIndicatorHandles()
{
if(g_maHandle != INVALID_HANDLE)
return true;
ResetLastError();
g_maHandle = iMA(_Symbol,
PERIOD_CURRENT,
20,
0,
MODE_EMA,
PRICE_CLOSE);
if(g_maHandle == INVALID_HANDLE)
{
Print("DIAG/INIT: event=HANDLE_CREATE_FAIL",
" reason=INVALID_HANDLE",
" error=", GetLastError());
return false;
}
Print("DIAG/INIT: event=HANDLE_CREATE_OK",
" kind=MA");
return true;
}
int OnInit()
{
if(!InitRuntime())
return INIT_FAILED;
if(!ValidateInputs())
return INIT_FAILED;
if(!CreateIndicatorHandles())
return INIT_FAILED;
g_runtimeReady = true;
Print("DIAG/INIT: event=RUNTIME_INIT_OK");
return INIT_SUCCEEDED;
}この例では、OnInitを「初期化処理の入口」として扱い、実際の処理はhelperへ分けています。
このコードの役割
| 処理 | 役割 |
|---|---|
| InitRuntime | runtime状態を初期化し、起動ログを出す |
| ValidateInputs | input設定の不整合を確認する |
| CreateIndicatorHandles | 必要なhandleを作成する |
| RUNTIME_INIT_OK | EAが初期化完了したことを確認する |
| INIT_FAILED | 準備できない場合に安全側で起動失敗にする |
実コード断片2:OnTickを入口だけにする
以下は、OnTickに全処理を書かず、tickごとの処理をhelperへ分ける例です。中核エントリー条件は含めていません。
// 学習用に一般化したOnTick構造例です。
// 実運用EAの完成ソースではありません。
struct RuntimeSnapshot
{
bool ready;
bool newBar;
string reason;
};
RuntimeSnapshot BuildRuntimeSnapshot()
{
RuntimeSnapshot snap;
snap.ready = false;
snap.newBar = false;
snap.reason = "INIT";
if(!g_runtimeReady)
{
snap.reason = "RUNTIME_NOT_READY";
return snap;
}
snap.ready = true;
snap.reason = "OK";
return snap;
}
void RunManagedExit()
{
// 実際は建値移動、Trail、保護決済などを別helperで扱います。
}
void RunEntryEvaluation()
{
// 実際はsignal評価、entry guard、executionを分けて扱います。
// 中核エントリー条件はこの記事では扱いません。
}
void OnTick()
{
RuntimeSnapshot snap = BuildRuntimeSnapshot();
if(!snap.ready)
{
Print("DIAG/TICK: event=SKIP",
" reason=", snap.reason);
return;
}
RunManagedExit();
RunEntryEvaluation();
}OnTickでは、まずruntimeが準備できているか確認し、準備できていない場合は理由付きで見送ります。
このコードの役割
| 処理 | 役割 |
|---|---|
| BuildRuntimeSnapshot | tick処理に入れる状態か確認する |
| RUNTIME_NOT_READY | 初期化未完了時に処理を止める理由 |
| RunManagedExit | 既存ポジション保護や決済管理を扱う |
| RunEntryEvaluation | 新規Entry候補の評価を扱う |
| OnTick | 各helperを呼ぶ入口として最小化する |
EAでは、新規Entry停止中でも既存ポジション保護は継続したい場合があります。そのため、entry処理とexit処理は分けておくと確認しやすくなります。
実コード断片3:OnDeinitを最小化する
以下は、OnDeinitで広範囲cleanupを行わず、終了時の処理を最小化する学習用コード例です。
// 学習用に一般化したOnDeinit最小化例です。
// 実運用EAの完成ソースではありません。
void ReleaseIndicatorHandles()
{
if(g_maHandle != INVALID_HANDLE)
{
IndicatorRelease(g_maHandle);
g_maHandle = INVALID_HANDLE;
Print("DIAG/DEINIT: event=HANDLE_RELEASE",
" kind=MA");
}
}
void DeleteStatusObjectsOnly()
{
// 自EA所有が明確な軽量ステータスObjectだけを対象にします。
// 広範囲のObject走査・全削除は行いません。
}
void OnDeinit(const int reason)
{
Comment("");
EventKillTimer();
DeleteStatusObjectsOnly();
ReleaseIndicatorHandles();
g_runtimeReady = false;
Print("DIAG/DEINIT: event=MINIMAL_CLEANUP",
" reason=", reason);
}OnDeinitは、終了時に安全にできる処理だけを行います。Object cleanupをすべてここへ詰め込むのではなく、必要に応じて次回OnInit時のstartup auditで確認する設計が安全な場合があります。
このコードの役割
| 処理 | 役割 |
|---|---|
| Comment(“”) | チャート左上コメントを消す |
| EventKillTimer | timer処理を停止する |
| DeleteStatusObjectsOnly | 自EA所有が明確な軽量Objectだけ整理する |
| ReleaseIndicatorHandles | 作成済みhandleをIndicatorReleaseする |
| MINIMAL_CLEANUP | 終了時処理が最小化されていることをログに残す |
実コード断片4:OnTimerを分ける考え方
EAによっては、OnTickだけでなくOnTimerを使って、重い処理や定期確認を分けることがあります。
以下は、OnTimerを使う考え方の学習用例です。
// 学習用に一般化したOnTimer分離例です。
// 実運用EAの完成ソースではありません。
datetime g_lastSummaryTime = 0;
bool ShouldPrintRuntimeSummary()
{
datetime nowTime = TimeCurrent();
if((nowTime - g_lastSummaryTime) < 300)
return false;
g_lastSummaryTime = nowTime;
return true;
}
void PrintRuntimeSummary()
{
Print("DIAG/RUNTIME: event=SUMMARY",
" ready=", g_runtimeReady,
" symbol=", _Symbol);
}
void OnTimer()
{
if(!g_runtimeReady)
return;
if(ShouldPrintRuntimeSummary())
PrintRuntimeSummary();
// 外部通信や重い処理を使う場合も、
// OnTickとは責務を分けて設計します。
}OnTimerを使う場合でも、外部連携、認証、通知、ログ集計などは責務を分けて設計します。この記事では外部制御の詳細や認証ロジックは扱いません。
ログで確認するポイント
OnInit・OnTick・OnDeinitを責務分離しているEAでは、Expertsログで次の流れを確認できます。
| ログ | 見るポイント |
|---|---|
| DIAG/INIT: START | EAが起動処理に入ったか |
| DIAG/INIT: VALIDATION_OK | inputや初期設定の確認が通ったか |
| DIAG/INIT: HANDLE_CREATE_OK | インジケーターハンドルが作成できたか |
| DIAG/INIT: RUNTIME_INIT_OK | runtimeが稼働可能状態になったか |
| DIAG/TICK: SKIP | tick処理が見送られた理由が分かるか |
| DIAG/RUNTIME: SUMMARY | 定期的な稼働状態が確認できるか |
| DIAG/DEINIT: HANDLE_RELEASE | handle解放が行われたか |
| DIAG/DEINIT: MINIMAL_CLEANUP | 終了処理が最小化されているか |
ログ確認の基本は、次のページも参考にしてください。
よくある失敗例
失敗1:OnTickへすべて書いてしまう
OnTickに判定、発注、決済、表示、ログ、外部通信をすべて書くと、処理の見通しが悪くなります。原因調査もしにくくなるため、helperへ分ける設計が重要です。
失敗2:OnInitで準備失敗しているのにOnTickが動く
handle作成やvalidationに失敗しているのにOnTickが動くと、不正な状態で処理が進む可能性があります。runtime readyフラグやINIT_FAILEDを使い、安全側で止めます。
失敗3:OnDeinitで全Objectを消そうとする
EA削除時に広範囲cleanupを行うと、フリーズや他Object巻き込みの原因になる場合があります。OnDeinitは最小化し、残存Objectはstartup auditで確認する方が安全な場面があります。
失敗4:handle解放を忘れる
iMA、iRSI、iCustomなどで作成したhandleは、不要になった時にIndicatorReleaseする設計が必要です。解放後はINVALID_HANDLEへ戻しておくと誤使用を防ぎやすくなります。
失敗5:ログが成功時だけになっている
起動成功ログだけでは、動かない原因を追えません。INIT失敗、TICK見送り、DEINIT処理、handle作成失敗などをreason付きで出すと確認しやすくなります。
開発依頼前に整理する情報
EA開発を依頼する場合、イベント構造の方針を最初に整理しておくと、仕様のすれ違いを減らせます。
- OnInitでどこまでvalidationするか
- インジケーターハンドルをOnInitで作るか
- OnTickではバー確定型かリアルタイム型か
- OnTickで毎回実行する処理と、状態変化時だけ実行する処理
- OnTimerを使うか
- OnDeinitでどこまで片付けるか
- Object cleanupをOnDeinitで行うか、startup auditへ回すか
- INIT / TICK / DEINITログをどの粒度で出すか
仕様書、setファイル、ログ、スクリーンショットを整理する場合は、次のページも確認してください。
MT5開発依頼前に用意する資料まとめ|仕様書・setファイル・ログ・スクリーンショット
公開しない情報
この記事では、MQL5 EAのイベント構造を学ぶために、公開可能な範囲だけを一般化して解説しています。以下は公開しません。
- 完成EAソース全文
- 外部シート参照による動作制御ロジック
- 外部シートCSV取得、解析、時間枠、方向制御ロジック
- 認証、口座許可判定、購入者管理ロジック
- endpoint、token、URL、Google Sheet ID、Webhook URL、GAS URL
- APIキー、認証トークン、口座番号、サーバー名
- 実運用set値
- 中核エントリー条件
- 独自優位性のある売買判定式
- 利益保証、勝率保証、推奨ロット、推奨銘柄、推奨エントリー
本記事は、投資判断や売買指示ではなく、MQL5 EA開発におけるOnInit・OnTick・OnDeinitの責務分離を理解するための技術解説です。
FAQ
OnInitでは何をするべきですか?
OnInitでは、初期化、input確認、handle作成、timer設定、起動ログなどを行います。重い売買判定や発注処理はOnInitに入れず、準備と検査に限定する方が安全です。
OnTickでは何をするべきですか?
OnTickでは、現在状態の取得、既存ポジション管理、signal評価、entry guard、execution候補などを扱います。ただし、処理を直接書き込みすぎず、helperへ分けることが重要です。
OnDeinitで全Objectを削除した方がよいですか?
必ずしもそうではありません。広範囲cleanupはEA削除時の不安定化や他Object巻き込みにつながる場合があります。OnDeinitは最小化し、残存Objectは次回起動時のstartup auditで扱う設計が安全な場合があります。
OnTimerは必ず必要ですか?
必須ではありません。ただし、定期ログ、状態監視、外部連携、重い確認処理などをOnTickから分離したい場合は、OnTimerを使う設計が有効です。
runtime readyフラグは必要ですか?
handle作成やvalidationが失敗した状態でOnTick処理へ進まないようにするため、runtime readyのような状態フラグを持つと安全です。
このコードは完成EAとして使えますか?
いいえ。この記事のコード例は、OnInit・OnTick・OnDeinitの責務分離を理解するために簡略化した学習用コードです。完成EAとしてそのまま使うことは想定していません。
実際のEAソース全文は公開されていますか?
公開していません。完成EAソース全文、外部シート制御、認証、endpoint、token、URL、実運用set値、中核エントリー条件は公開対象外です。
関連ページ
| ページ | 確認できること |
|---|---|
| MQL5ログファースト設計を実コードで解説 | INIT / TICK / DEINITログをreason付きで確認する考え方 |
| MT5のログ確認方法|Experts・Journalの見方 | ExpertsログとJournalログの基本確認 |
| MT5開発依頼前に用意する資料まとめ|仕様書・setファイル・ログ・スクリーンショット | 開発相談前に整理する資料 |
| 不具合報告・調査依頼について | 不具合相談前に送る情報 |
| 開発・改修の相談ページを見る | EA・インジケーター開発や既存改修の相談導線 |
| よくある質問 | 導入、確認、相談前のFAQ |
| 免責事項 | 投資判断、損益、利用上の注意に関する確認 |
| 投資助言を行わない方針 | 売買助言ではなく技術支援として扱う方針 |
まとめ
MQL5 EAの基本構造では、OnInit・OnTick・OnDeinitの役割を分けることが重要です。
特に、次の4点を分けておくと、原因調査と保守がしやすくなります。
- OnInit:初期化、validation、handle作成、起動ログ
- OnTick:状態取得、既存ポジション管理、判定、見送り、実行候補
- OnDeinit:timer停止、handle解放、軽量終了処理
- OnTimer:必要に応じて定期ログや重い処理を分離
イベント関数へ処理を詰め込みすぎると、どこで失敗しているのか追いにくくなります。OnInitでは準備、OnTickでは実行入口、OnDeinitでは最小終了処理という形に分けることで、EAの挙動をログから確認しやすくなります。
完成EAの中核ロジックを公開しなくても、イベント構造と責務分離を理解しておくことで、EAが起動しない、値が取れない、tick処理が重い、削除時に不安定になるといった問題を切り分けやすくなります。

