技術講座

MQL5イベント処理完全ガイド|OnTimer・OnTradeTransaction・OnChartEventの使い分け

EAファンクラブ

MQL5でEA、インジケーター、パネル型ツール、通知ツール、診断ツールを開発する時は、イベント処理の設計が非常に重要です。MQL5では、EAが起動した時、価格が更新された時、タイマーが動いた時、注文や約定が発生した時、チャート上のボタンが押された時、インジケーターが再計算された時などに、それぞれ対応するイベント関数が呼び出されます。

代表的なイベント関数には、OnInit、OnTick、OnTimer、OnDeinit、OnTradeTransaction、OnChartEvent、OnCalculateがあります。これらを正しく分けて使わないと、EAが起動しない、注文しない、通知が連続送信される、ボタンが反応しない、終了時に状態が残る、インジケーター値がEA側で取れない、ログが多すぎて原因を追えないといった問題が起きやすくなります。

特にEA開発では、すべての処理をOnTickに詰め込む構成がよくあります。小さなサンプルでは動作しますが、実務では、シグナル判定、フィルター、発注前チェック、注文処理、決済処理、通知、外部連携、パネル更新、ログ出力が混在し、後から不具合を追いにくくなります。

この記事では、MQL5のイベント処理について、OnInit、OnTick、OnTimer、OnDeinit、OnTradeTransaction、OnChartEvent、OnCalculateの役割、EA・インジケーター・パネルEA・通知ツールでの使い分け、signal / filter / risk / execution / exit / notification / UI の責務分離、ログ設計、バックテストと実チャートでの確認方法を実務目線で整理します。

なお、この記事はMT5 / MQL5のEA開発、インジケーター開発、パネルEA、通知・診断ツール、ログ確認を目的とした技術記事です。特定の売買判断、利益、勝率、損失回避、推奨ロット、推奨銘柄を案内するものではありません。

この記事で確認すること

  • MQL5イベント処理の全体像
  • OnInitで行う初期化、input検証、ハンドル作成
  • OnTickで行う価格更新時の判定処理
  • OnTimerで行う定期監視、通知、外部連携、軽量化
  • OnDeinitで行う終了処理、タイマー解除、リソース解放
  • OnTradeTransactionで注文・約定・決済イベントを追跡する方法
  • OnChartEventでボタン・パネル・クリック操作を扱う方法
  • OnCalculateでインジケーター計算を行う時の考え方
  • OnTickに処理を詰め込みすぎない設計
  • signal / filter / risk / execution / exit / notification / UI の責務分離
  • EA、インジケーター、半裁量EA、パネルEA、通知ツールでの使い分け
  • イベントごとのログ設計
  • バックテストで確認できること、実チャートで確認すべきこと
  • 開発依頼前に整理する情報

MQL5のイベント処理とは

MQL5のイベント処理とは、MT5側で発生した出来事に応じて、あらかじめ決められた関数が呼び出される仕組みです。EAがチャートに適用された時、価格が更新された時、タイマーが動いた時、注文や約定が発生した時、チャート上のボタンが押された時などに、対応するイベント関数が実行されます。

EAでは、イベント関数を単なる入口として扱い、実際の処理は専用関数へ分ける設計が扱いやすいです。たとえば、OnTickは価格更新時の入口、OnTimerは定期監視の入口、OnChartEventはUI操作の入口、OnTradeTransactionは取引イベント確認の入口として使います。

イベント処理を整理すると、EAの状態管理、発注前チェック、注文結果確認、通知、外部連携、UI操作、終了処理を分けられます。これは、通常のEAだけでなく、インジケーター、半裁量EA、パネルEA、通知・診断ツール、コピーEA、検証補助ツールでも重要です。

イベント関数主な役割実務上の使いどころ
OnInit初期化処理。input検証、ハンドル作成、タイマー設定、初期ログ。
OnTick価格更新時の処理。シグナル判定、フィルター確認、発注判断。
OnTimer定期処理。監視、通知、状態更新、外部連携、重い処理の分離。
OnDeinit終了処理。タイマー解除、ハンドル解放、オブジェクト削除、終了ログ。
OnTradeTransaction取引イベント処理。注文、約定、決済、変更イベントの追跡。
OnChartEventチャートイベント処理。ボタン、パネル、クリック、キー操作。
OnCalculateインジケーター計算。バッファ出力、EMPTY_VALUE、EA連携。

イベント関数は入口、実処理は分離する

MQL5開発では、イベント関数そのものに長い処理を書きすぎないことが重要です。OnTickに何百行も処理を書くと、どこで判定し、どこで停止し、どこで注文し、どこで通知したのかを追いにくくなります。

実務では、イベント関数は入口として使い、処理本体は関数へ分けます。たとえば、OnTickでは「新バー判定」「シグナル判定」「フィルター判定」「発注前チェック」「注文処理」を順番に呼び出すだけにし、各処理の中身は別関数へ分離します。

この分離により、ログ名、責務、検証範囲が明確になります。EAが注文しない時も、シグナルが不成立なのか、スプレッドで止まったのか、ロットが不正なのか、OrderCheckで失敗したのか、OrderSendで拒否されたのかを追いやすくなります。

void OnTick()
{
   if(!IsTradingContextReady())
      return;

   if(!IsNewBar(_Symbol, PERIOD_CURRENT))
      return;

   int signal = EvaluateSignal();

   if(signal == 0)
   {
      Print("ENTRY_SKIP reason=NO_SIGNAL");
      return;
   }

   if(!PassEntryFilters(signal))
      return;

   if(!CheckRiskBeforeEntry(signal))
      return;

   ExecuteEntry(signal);
}

この例では、OnTick内にすべての処理を書かず、状態確認、シグナル判定、フィルター、リスク確認、注文処理を分けています。実際のEAでは、それぞれの関数内にログを入れることで、どの段階で止まったかを確認しやすくなります。

OnInitで行う初期化と検証

OnInitは、EAやインジケーターがチャートへ適用された時に呼ばれる初期化イベントです。ここでは、input値の妥当性確認、銘柄仕様確認、インジケーターハンドル作成、ファイルや外部連携設定の確認、タイマー設定、初期ログ出力を行います。

OnInitで失敗を検出できる設計にしておくと、OnTickで動き続けてから失敗に気づくよりも安全です。たとえば、ロット設定が不正、対象銘柄が存在しない、CopyBuffer用のハンドルが作れない、WebRequest URLが未設定、CSV保存先が不正、Discord通知設定が空、といった問題はOnInit時点で検出しやすいです。

OnInitで検出すべき問題を無視すると、EAはチャート上で動いているように見えても、実際にはエントリーしない、通知しない、ログが出ない、インジケーター値が取得できない状態になります。初期化ログは、後から原因を追うためにも重要です。

input double InpLots = 0.10;
input int InpTimerSeconds = 10;

int OnInit()
{
   PrintFormat("INIT_START program=%s symbol=%s period=%d lots=%.2f timer=%d",
               MQLInfoString(MQL_PROGRAM_NAME),
               _Symbol,
               Period(),
               InpLots,
               InpTimerSeconds);

   if(InpLots <= 0.0)
   {
      PrintFormat("INIT_FAIL reason=BAD_LOTS lots=%.2f", InpLots);
      return INIT_FAILED;
   }

   if(InpTimerSeconds <= 0)
   {
      PrintFormat("INIT_FAIL reason=BAD_TIMER timer=%d", InpTimerSeconds);
      return INIT_FAILED;
   }

   EventSetTimer(InpTimerSeconds);

   PrintFormat("INIT_OK symbol=%s timer=%d", _Symbol, InpTimerSeconds);

   return INIT_SUCCEEDED;
}

この例では、inputのロットとタイマー秒数を確認しています。実際のEAでは、ロット、Magic Number、対象銘柄、時間足、WebRequest URL、ファイル出力、インジケーターハンドル、通知ON/OFF、EA稼働モードなどをOnInitで検証します。

OnInitで確認したい項目

OnInitでは、EAが安全に動ける前提がそろっているかを確認します。ここで止めるべき設定不備を通してしまうと、OnTickやOnTimerで原因不明の停止や失敗が発生します。

確認項目内容ログ例
input検証ロット、期間、ON/OFF、閾値。INIT_FAIL reason=BAD_INPUT
銘柄仕様point、digits、volume step、StopLevel。SYMBOL_SPEC
インジケーターハンドルiMA、iATR、iRSI、iCustom。HANDLE_OK / HANDLE_FAIL
タイマーEventSetTimerの秒数。INIT_OK timer=10
通知設定通知ON/OFF、Webhook設定有無。NOTIFY_CONFIG
ファイル出力CSVログ、保存先、ファイル名。FILE_CONFIG
外部連携WebRequest許可、送信先設定。EXTERNAL_CONFIG

OnInit失敗時の設計

OnInitで重大な設定不備を検出した場合は、INIT_FAILEDを返してEAを停止させる設計が安全です。たとえば、インジケーターハンドルが作れない、対象銘柄が選択できない、ロット設定が不正、必須の外部連携URLが空、ファイル保存先が使えないといった状態でEAを動かし続けると、OnTick側で失敗を繰り返すだけになります。

一方で、通知設定がOFF、任意の補助機能が未設定、詳細ログが無効といった軽微な状態は、EA停止ではなく警告ログだけにする場合もあります。どの条件で停止し、どの条件で警告にするかを仕様として分けることが重要です。

状態対応例理由
ロットが0以下INIT_FAILED。発注処理が成立しないため。
対象銘柄が存在しないINIT_FAILED。価格取得や注文ができないため。
必須インジケーターハンドル失敗INIT_FAILED。シグナル判定ができないため。
通知設定OFF警告または通常ログ。EA本体の売買処理には影響しない場合があるため。
詳細ログOFF通常動作。運用設定として許容できるため。
任意CSV出力OFF通常動作。補助機能として扱えるため。

OnTickで行う価格更新時の処理

OnTickは、対象チャートの銘柄でティックが更新された時に呼ばれます。EAでは、価格更新に応じたシグナル判定、フィルター判定、発注判断、決済判断などをOnTickで行うことが多いです。

ただし、OnTickは呼び出し頻度が高いため、重い処理や外部連携を入れすぎると、動作が重くなったり、ログが大量に出たり、通知が連続送信されたりします。OnTickでは、価格更新に直接関係する処理を中心にし、定期監視や画面更新、外部送信などはOnTimerへ分ける設計が扱いやすいです。

また、同じバー内で何度もエントリー判定したくない場合は、新バー判定を組み合わせます。OnTickはティックごとに呼ばれるため、同じバー内で条件が成立している間ずっと注文を出してしまうような実装は避ける必要があります。

bool IsNewBar(string symbol, ENUM_TIMEFRAMES timeframe)
{
   static datetime last_bar_time = 0;

   datetime current_bar_time = iTime(symbol, timeframe, 0);

   if(current_bar_time == 0)
   {
      PrintFormat("NEWBAR_FAIL symbol=%s tf=%d last_error=%d",
                  symbol,
                  timeframe,
                  GetLastError());
      return false;
   }

   if(current_bar_time != last_bar_time)
   {
      PrintFormat("NEWBAR symbol=%s tf=%d time=%s prev=%s",
                  symbol,
                  timeframe,
                  TimeToString(current_bar_time),
                  TimeToString(last_bar_time));

      last_bar_time = current_bar_time;
      return true;
   }

   return false;
}

この例では、現在足の開始時刻を使って新バー判定を行っています。単一銘柄・単一時間足EAならstatic変数1つでも扱えますが、複数銘柄・複数時間足EAでは、symbolとtimeframeごとにlast_bar_timeを持つ必要があります。

OnTickに入れる処理・入れすぎない処理

OnTickには、価格更新に直結する処理を入れるのが基本です。シグナル判定、現在価格の確認、スプレッド確認、発注前チェック、決済条件確認などはOnTickと相性があります。一方で、重い集計、外部送信、パネル全体更新、長いファイル書き込み、過剰なログ出力はOnTickに入れすぎない方が安全です。

OnTick向き理由注意点
シグナル判定価格更新に応じて変化するため。現在足か確定足かを明確にします。
スプレッド確認発注直前条件として重要なため。見送り理由をログに残します。
発注前チェック注文直前に確認する必要があるため。ロット、証拠金、SL/TP距離を確認します。
決済条件確認価格変動に応じて判断するため。トレーリングや建値移動は重複処理に注意します。
OnTickに入れすぎない処理理由代替先
外部通知の連続送信ティックごとに送信される可能性があります。OnTimer、OnTradeTransaction。
重いファイル書き込みティック頻度が高いとログが肥大化します。OnTimer、条件付きログ。
パネル全体更新描画負荷が増えます。OnTimer。
全銘柄の重いスキャン多銘柄EAで処理が重くなります。OnTimer、分割処理。
長いWebRequest応答待ちで動作に影響する可能性があります。送信間隔制御、キュー処理。

OnTickでの重複発注防止

OnTickはティックごとに呼ばれるため、条件が成立している間に何度も注文してしまう危険があります。重複発注を防ぐには、新バー判定、最大ポジション数、同一Magic Numberの保有確認、直近発注時刻、クールダウン秒数などを組み合わせます。

特に、現在足の条件を使うEAでは、ティックごとに条件が成立し続けることがあります。その状態でOrderSendやCTrade.Buyを毎回呼ぶと、同じ方向に複数ポジションを持ってしまう可能性があります。

bool CanSendEntry(datetime last_entry_time, int cooldown_seconds)
{
   datetime now_time = TimeCurrent();

   if((now_time - last_entry_time) < cooldown_seconds)
   {
      PrintFormat("ENTRY_BLOCK reason=COOLDOWN now=%s last=%s cooldown=%d",
                  TimeToString(now_time),
                  TimeToString(last_entry_time),
                  cooldown_seconds);
      return false;
   }

   return true;
}

この例では、直近発注時刻から一定秒数が経過するまでエントリーを止めています。実際には、同一銘柄、同一Magic Number、同一方向、同一バーでの発注可否も仕様として整理します。

OnTimerで定期監視と軽量化を行う

OnTimerは、EventSetTimerで設定した周期に応じて呼び出されるイベントです。価格更新に依存しない定期処理を行う時に使います。たとえば、稼働状態の監視、通知送信、外部シートへの記録、パネル更新、日次リセット、ポジション監視、接続状態確認などをOnTimerへ分けると整理しやすくなります。

OnTimerを使うと、OnTickの負荷を軽くできます。たとえば、毎ティックで全ポジションを集計するのではなく、5秒または10秒に1回だけ監視ログを出す設計にできます。通知や外部連携も、イベント発生時に即送るのではなく、送信キューへ積んでOnTimerで処理する構成にすると、連続送信を抑えやすくなります。

ただし、OnTimerも万能ではありません。周期が短すぎる場合、重い処理を入れすぎた場合、外部連携の失敗時に無制限リトライする場合は、動作が不安定になる可能性があります。OnTimerでは、周期、処理量、ログ量、リトライ回数を設計してください。

void OnTimer()
{
   static int timer_count = 0;
   timer_count++;

   PrintFormat("TIMER_HEARTBEAT count=%d symbol=%s server_time=%s",
               timer_count,
               _Symbol,
               TimeToString(TimeCurrent()));

   CheckRuntimeStatus();
   UpdatePanelIfNeeded();
   FlushNotificationQueue();
}

この例では、OnTimerで稼働状態確認、パネル更新、通知キュー処理を呼び出しています。実際のEAでは、通知キューの重複防止、送信失敗時の再試行回数、ログ出力頻度を制御します。

OnTimerでやりやすい処理

処理内容注意点
ハートビートログEAが稼働中であることを一定間隔で記録します。頻度を高くしすぎないようにします。
パネル更新損益、状態、接続状況などを更新します。毎ティック更新より軽量化しやすいです。
通知キュー処理Discord通知やメール通知を順番に送ります。重複送信と連続送信に注意します。
日次リセット日付変更時にカウントや状態を初期化します。サーバー時刻基準を明確にします。
ポジション監視一定間隔で保有状態を確認します。OnTickと二重処理にならないようにします。
外部連携CSV、Google Sheets、Webhookなどへ送信します。認証情報やURLをログに出さないようにします。

OnTimerと外部連携の注意点

Discord通知、Webhook通知、Google Sheets連携などをOnTimerで扱う場合は、送信間隔、送信件数、失敗時の再送、機密情報のマスクを設計します。外部連携は通信状態や送信先の応答に依存するため、EA本体の売買判定と強く結合しすぎない方が安全です。

通知や外部送信が失敗した場合、無制限に再送し続けるとログが肥大化し、送信先にも負荷をかけます。再送回数、再送間隔、破棄条件、手動確認ログを決めておくと、運用時の確認がしやすくなります。

確認項目内容ログ例
送信間隔何秒ごとに送るか。NOTIFY_WAIT interval=10
送信件数1回のOnTimerで何件処理するか。NOTIFY_FLUSH count=3
失敗回数再送を何回まで許可するか。NOTIFY_RETRY count=2
送信結果HTTP結果、内部結果。NOTIFY_RESULT status=200
機密情報URL、token、API key。ログへ出さない。

OnDeinitで終了処理を行う

OnDeinitは、EAやインジケーターが終了する時、チャートから外された時、パラメータ変更、時間足変更、再コンパイル、MT5終了などのタイミングで呼ばれます。OnInitで設定したものは、OnDeinitで解除するという考え方が基本です。

OnDeinitでは、EventKillTimer、IndicatorRelease、ファイルクローズ、オブジェクト削除、終了ログ出力などを行います。特にOnTimerを使っている場合は、EventKillTimerを忘れないようにします。インジケーターハンドルを作成した場合は、必要に応じてIndicatorReleaseを行います。

また、終了理由をログに残すことで、ユーザー操作で外されたのか、パラメータ変更なのか、時間足変更なのか、MT5終了なのかを確認しやすくなります。原因不明の再初期化が発生している時にも、OnDeinitログは重要です。

void OnDeinit(const int reason)
{
   EventKillTimer();

   PrintFormat("DEINIT reason=%d symbol=%s time=%s",
               reason,
               _Symbol,
               TimeToString(TimeCurrent()));

   ReleaseIndicatorHandles();
   DeletePanelObjects();
}

この例では、タイマー解除、終了ログ、ハンドル解放、パネルオブジェクト削除を行っています。実際のEAでは、OnInitで作成したリソースを一覧化し、OnDeinitで対応する解除処理を行うと管理しやすくなります。

OnDeinitのreasonをログに残す

OnDeinitのreasonは、EAやインジケーターがなぜ終了したのかを確認する手がかりになります。パラメータ変更、チャート変更、時間足変更、再コンパイル、チャートからの削除、MT5終了などでOnDeinitが呼ばれるため、reasonをログに残しておくと、再初期化や停止の原因を追いやすくなります。

特に、OnInitとOnDeinitが短時間に繰り返されている場合、ユーザー操作、チャート切り替え、テンプレート適用、インジケーター再読み込みなどが関係していることがあります。終了ログと初期化ログをセットで見ると、動作の流れを確認できます。

OnTradeTransactionで注文・約定・決済イベントを追跡する

OnTradeTransactionは、取引に関するイベントが発生した時に呼ばれる関数です。注文送信、注文変更、約定、決済、ポジション変化などを追跡する時に使います。EAでOrderSendやCTradeを使って注文した後、実際にどのような取引イベントが発生したかを確認するには、OnTradeTransactionが役立ちます。

注文処理では、OrderSendやCTradeの戻り値だけを見て終わりにすると、実際に約定したのか、部分約定したのか、決済イベントが発生したのか、注文変更が行われたのかを追いにくくなります。OnTradeTransactionを使うと、注文・約定・決済イベントをログで追いやすくなります。

ただし、OnTradeTransactionは取引イベントごとに呼ばれるため、ログを出しすぎると読みづらくなります。trans.type、order、deal、symbol、volume、price、request、result.retcodeなど、必要な情報を整理して出力します。

void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
{
   PrintFormat("TRADE_TX type=%d order=%I64u deal=%I64u symbol=%s volume=%.2f price=%.5f retcode=%d",
               trans.type,
               trans.order,
               trans.deal,
               trans.symbol,
               trans.volume,
               trans.price,
               result.retcode);
}

この例では、取引イベントの基本情報をログに出しています。実務では、OrderCheckログ、OrderSendログ、OnTradeTransactionログを同じMagic Numberや注文番号で追えるようにしておくと、注文失敗や約定ズレの調査が進めやすくなります。

OnTradeTransactionで通知を行う時の注意

OnTradeTransactionは、約定通知や決済通知の起点として使いやすいイベントです。たとえば、注文が成立した時にDiscordへ通知する、決済時にCSVへ記録する、ポジション変更時にパネルを更新する、といった処理に使えます。

ただし、OnTradeTransaction内で直接WebRequestを連続実行すると、イベントが多い時に通知が詰まる可能性があります。実務では、OnTradeTransactionで通知候補をキューに入れ、OnTimerで送信する設計にすると安定しやすくなります。

通知ログでは、Webhook URL、APIキー、トークンなどの機密情報を出してはいけません。ログには、通知種別、対象注文、対象銘柄、送信結果、HTTPステータス、エラー種別などに限定するのが安全です。

取引イベントで確認したいこと内容ログ例
注文発生注文番号、銘柄、方向、ロット。TRADE_TX_ORDER
約定発生deal、価格、ロット。TRADE_TX_DEAL
決済発生決済deal、損益、理由。TRADE_TX_CLOSE
注文変更SL/TP変更、注文価格変更。TRADE_TX_MODIFY
通知候補送信対象イベント。NOTIFY_ENQUEUE

OnChartEventでボタン・パネル操作を扱う

OnChartEventは、チャート上のイベントを処理するための関数です。マウスクリック、オブジェクトクリック、キー入力、チャート変更などを受け取ることができます。パネルEA、半裁量EA、裁量補助ツール、インジケーターパネルでは特に重要です。

たとえば、チャート上にBUYボタン、SELLボタン、全決済ボタン、通知ON/OFFボタン、表示切替ボタンを配置する場合、OnChartEventでどのボタンが押されたかを確認します。

注意点として、UI操作と実際の注文処理を直接混ぜすぎないことが重要です。OnChartEventでは「ユーザーが何を押したか」を受け取り、実際の発注前チェックや注文処理は別関数へ渡す構成にすると安全です。

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      PrintFormat("CHART_EVENT object_click name=%s", sparam);

      if(sparam == "btn_buy")
      {
         Print("UI_ACTION request=BUY_BUTTON");
         RequestManualEntry(ORDER_TYPE_BUY);
      }

      if(sparam == "btn_close_all")
      {
         Print("UI_ACTION request=CLOSE_ALL_BUTTON");
         RequestCloseAllPositions();
      }
   }
}

この例では、ボタン名に応じて手動操作リクエストを発行しています。実際のパネルEAでは、ボタン押下、確認状態、発注前チェック、注文結果ログを分けて設計します。ボタンを押した瞬間に無条件で注文する設計は避ける方が安全です。

OnChartEventで扱う主なイベント

イベント主な用途注意点
オブジェクトクリックボタン、ラベル、パネル操作。オブジェクト名を明確に管理します。
キー入力ショートカット操作。誤操作防止が必要です。
マウス移動補助表示、ツールチップ。頻度が高いため処理を軽くします。
チャート変更サイズ変更、表示更新。パネル再配置に使うことがあります。
カスタムイベント内部イベント連携。イベントIDと引数を管理します。

パネルEAでのOnChartEvent設計

パネルEAでは、OnChartEventが中心になります。ユーザーがボタンを押した時、EA内部では「ボタンが押された」というUIイベントを受け取り、その後に発注前チェック、確認処理、注文処理、ログ出力へ進みます。

重要なのは、UIイベントと注文実行を混同しないことです。ボタン押下はあくまでユーザー操作の入力です。実際に注文する前には、ロット、証拠金、スプレッド、StopLevel、最大ポジション数、取引時間、AutoTrading状態を確認します。

また、パネル表示の更新はOnTimerへ分けると軽量化しやすくなります。OnChartEventでは操作受付、OnTimerでは表示更新、OnTradeTransactionでは取引結果反映というように分けると、パネルEAの保守性が高くなります。

OnCalculateとインジケーターイベント

OnCalculateは、インジケーターの計算処理で使われるイベント関数です。EAのOnTickとは異なり、インジケーターではOnCalculateで各バーの値を計算し、SetIndexBufferで登録したバッファへ値を出力します。

EA連携を前提にしたインジケーターでは、OnCalculateでどのバッファに何を入れるかが重要です。たとえば、バッファ0を買いサイン、バッファ1を売りサイン、バッファ2をフィルター値として設計し、EA側ではiCustomとCopyBufferで取得します。

OnCalculateでは、rates_total、prev_calculated、EMPTY_VALUE、現在足と確定足、リペイントの有無を確認します。EA側がCopyBufferで取得する時に、現在足を使うのか確定足を使うのかを仕様として明確にしてください。

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[])
{
   if(rates_total < 50)
   {
      PrintFormat("CALC_SKIP reason=NOT_ENOUGH_BARS rates_total=%d", rates_total);
      return 0;
   }

   int start = 0;

   if(prev_calculated > 0)
      start = prev_calculated - 1;

   for(int i = start; i < rates_total; i++)
   {
      // バッファ計算を行います。
   }

   PrintFormat("CALC_OK rates_total=%d prev_calculated=%d start=%d",
               rates_total,
               prev_calculated,
               start);

   return rates_total;
}

OnCalculateのログは、開発中には有効ですが、通常運用で毎回大量に出すと読みづらくなります。検証用ログと通常ログを分ける設計が必要です。

EAでのイベント責務分離

EAでは、イベント関数と内部処理の責務を分けることが重要です。OnTickにすべてを書かず、signal、filter、risk、execution、exit、notification、ui、logのように処理単位を分けます。

イベント関数は入口として扱い、実際の判定や処理は専用関数へ渡す構成が保守しやすいです。これにより、OnTick、OnTimer、OnChartEventから同じ発注前チェック関数を呼ぶような設計もできます。

責務主な内容呼ばれるイベント例
signalエントリー候補の判定。OnTick、OnTimer。
filterスプレッド、時間帯、上位足、最大ポジション確認。OnTick。
riskロット、証拠金、SL/TP距離確認。OnTick、OnChartEvent。
executionOrderSend、CTrade、注文結果確認。OnTick、OnChartEvent。
exit決済、トレーリング、建値移動。OnTick、OnTimer。
notificationDiscord通知、メール通知、ログ通知。OnTimer、OnTradeTransaction。
uiボタン、パネル、表示更新。OnChartEvent、OnTimer。
log判定・注文・イベントの記録。全イベント。

インジケーターでのイベント処理

インジケーターでは、主にOnInit、OnCalculate、OnDeinitを使います。EAとは異なり、OnTickではなくOnCalculateで値を計算し、バッファへ出力します。EAからCopyBufferで値を取得する前提なら、OnCalculateでどのバッファへどの値を入れるかが重要です。

パネル型インジケーターやオブジェクト操作を伴うインジケーターでは、OnChartEventを使うこともあります。表示ON/OFF、ボタン操作、ラベル更新などを扱う場合は、UI処理と計算処理を分けると保守しやすくなります。

インジケーターでは、OnCalculateの再計算範囲、prev_calculated、EMPTY_VALUE、リペイント確認が重要です。EA連携する場合、現在足の値を使うのか、確定足の値を使うのかも明確にしてください。

半裁量EA・パネルEAでのイベント処理

半裁量EAやパネルEAでは、OnChartEventが重要になります。ユーザーがボタンを押した時に発注、決済、ロット変更、通知ON/OFF、トレーリングON/OFFなどを行う場合、UI操作と実処理を分けて設計します。

半裁量EAでは、ボタン操作を受けた後に、いきなり注文するのではなく、ロット、証拠金、StopLevel、スプレッド、取引時間、最大ポジション数などを確認します。自動EAと同じように、手動操作でも発注前チェックを通すことが重要です。

パネル更新はOnTimerへ分けると、OnTickの負荷を減らせます。OnChartEventでは操作受付、OnTimerでは表示更新、OnTickでは価格更新に関係する判定、OnTradeTransactionでは注文結果確認というように分けると整理しやすくなります。

通知・外部連携ツールでのイベント処理

Discord通知、Webhook通知、Google Sheets連携、CSVログ出力などの外部連携ツールでは、イベント処理の設計が特に重要です。OnTickごとに通知を送ると、同じ内容を連続送信してしまう可能性があります。

通知処理は、OnTradeTransactionで取引イベントを検出し、OnTimerで送信キューを処理するように分けると整理しやすくなります。外部連携の失敗時には、再送回数、送信間隔、失敗ログ、機密情報のマスクも必要です。

Webhook URL、APIキー、トークンなどの機密情報をログに出してはいけません。診断ログを残す場合も、URL全体ではなく、送信先種別、HTTP結果、送信件数、エラー種別、マスク済み識別子程度に抑えるのが安全です。

長時間稼働EAでのイベント設計

EAを長時間稼働させる場合、イベント処理の安定性が重要です。OnTickだけに依存すると、ティックが来ない時間帯に監視処理が止まることがあります。OnTimerを使うことで、価格更新が少ない時間帯でも一定間隔で状態確認を行えます。

長時間稼働では、ハートビートログ、接続状態、最終ティック時刻、最終注文時刻、最終通知時刻、外部連携の失敗回数などを管理すると、不具合調査がしやすくなります。

ただし、長時間稼働だからといってログを出し続けると、ExpertsログやCSVが肥大化します。通常時は簡潔にし、異常時だけ詳細ログを出す設計が現実的です。

イベント処理のログ設計

イベント処理では、どのイベントで何が起きたかをログで確認できることが重要です。OnInit、OnTick、OnTimer、OnDeinit、OnTradeTransaction、OnChartEvent、OnCalculateのログ名を分けておくと、不具合調査が進めやすくなります。

ログを出しすぎると読みづらくなるため、通常ログ、詳細ログ、エラー時ログを分ける設計が有効です。特にOnTickは呼び出し頻度が高いため、毎ティックで大量ログを出さないように注意してください。

ログ名残す内容目的
INIT_STARTEA名、銘柄、時間足、input概要。起動条件の確認。
INIT_FAIL初期化失敗理由。設定ミスや環境不備の確認。
INIT_OK初期化成功、タイマー設定、ハンドル作成。起動成功の確認。
TICK_SKIP判定を見送った理由。データ不足や条件未成立の確認。
NEWBAR新バー時刻。重複判定防止の確認。
TIMER_HEARTBEAT定期監視状態。長時間稼働確認。
TRADE_TX注文、約定、決済イベント。取引イベントの追跡。
CHART_EVENTボタン名、操作内容。UI操作の確認。
CALC_OKインジケーター計算範囲。OnCalculate確認。
DEINIT終了理由、解除処理。終了原因の確認。

バックテストで確認できること・確認しにくいこと

バックテストでは、OnInit、OnTick、OnDeinit、OnTimerの一部、注文処理の結果などを確認できます。一方で、OnChartEventのような手動操作系イベントや、外部サービス連携の実運用挙動は、バックテストだけでは確認しにくい場合があります。

イベント処理を検証する時は、バックテスト、デモ口座、実チャートで確認する項目を分けてください。特に通知、パネル操作、WebRequest、Discord連携、Google Sheets連携は、バックテストだけで完了扱いにしない方が安全です。

バックテストで確認できるのは、主にロジック、注文処理、ログ、OnTick中心の動作です。ユーザー操作、ボタン押下、外部通知、実際の通信許可、VPS上の稼働確認は、別途実チャートやデモ環境で確認します。

  • OnInitで初期化ログが出ているか確認する
  • OnTickで毎ティック処理が重くなっていないか確認する
  • 新バー判定が意図どおり動いているか確認する
  • OnTimerの周期とログ量を確認する
  • OnTradeTransactionで注文・約定・決済イベントを追跡できるか確認する
  • OnChartEventは実チャートでボタン操作を確認する
  • OnDeinitでタイマー解除やハンドル解放が行われているか確認する
  • 外部連携はバックテストだけで完了扱いにしない
  • バックテスト結果を将来成績保証として扱わない

よくある不具合と確認ポイント

イベント処理の不具合は、EAが動かない、注文しない、通知が出ない、パネルボタンが反応しない、終了時にエラーが出る、ログが大量に出る、といった形で表れます。どのイベントで問題が起きているかを分けて確認してください。

症状確認するイベント主な原因候補
EAが起動直後に止まるOnInit。input不正、ハンドル作成失敗、環境不備。
EAが注文しないOnTick。シグナル不成立、データ不足、フィルター停止。
ログが多すぎるOnTick、OnTimer。毎ティック詳細ログ、短すぎるタイマー周期。
通知が連続送信されるOnTimer、OnTradeTransaction。重複送信防止フラグ不足。
注文結果を追跡できないOnTradeTransaction。OrderSend結果だけを見ている。
ボタンが反応しないOnChartEvent。オブジェクト名違い、イベントID確認不足。
EA終了後も状態が残るOnDeinit。タイマー解除、オブジェクト削除、ハンドル解放不足。
インジケーター値がEAで取れないOnCalculate、OnInit。バッファ未設定、ハンドル作成失敗、EMPTY_VALUE。
バックテストと実チャートで違うOnTimer、OnChartEvent、外部連携。バックテストでは確認しにくいイベントがある。

イベント処理の実装チェック表

  • OnInitでinputと環境を検証している
  • OnInit失敗時にINIT_FAILEDを返している
  • OnTickに重い処理を詰め込みすぎていない
  • OnTickで新バー判定や重複発注防止を行っている
  • OnTimerで定期処理を分離している
  • OnTimerの周期が短すぎない
  • OnTradeTransactionで取引イベントを追跡している
  • OnChartEventでUI操作と注文処理を分離している
  • OnCalculateでインジケーターバッファを整理している
  • OnDeinitでタイマー解除とリソース解放を行っている
  • イベントごとのログ名を分けている
  • 外部連携の機密情報をログに出していない
  • バックテストと実チャートで確認すべき項目を分けている

開発依頼前に整理する情報

イベント処理を含むEAやツールの開発を依頼する場合は、どのイベントで何を行うかを整理してください。特に、OnTickで判定するのか、OnTimerで監視するのか、OnTradeTransactionで通知するのか、OnChartEventで手動操作するのかを分けることが重要です。

「ボタンを押したら注文したい」「約定したらDiscord通知したい」「一定間隔で状態を記録したい」「チャート上にパネルを表示したい」という要望は、それぞれ使うイベントが異なります。仕様段階でイベントと責務を分けておくと、実装後の不具合確認がしやすくなります。

整理する情報内容理由
EA / ツール種別自動売買EA、半裁量EA、パネルEA、通知ツールなど。使うイベントが変わるため。
初期化処理input検証、ハンドル作成、外部連携設定。OnInitの設計に必要です。
価格更新処理シグナル判定、フィルター、発注判断。OnTickの責務を決めるため。
定期処理監視、通知、状態更新、日次リセット。OnTimerの周期設計に必要です。
取引イベント注文、約定、決済、変更時の通知や記録。OnTradeTransactionの設計に必要です。
UI操作ボタン、パネル、クリック操作。OnChartEventの設計に必要です。
インジケーター計算OnCalculate、バッファ、EMPTY_VALUE。EA連携や表示確認に必要です。
終了処理タイマー解除、ハンドル解放、ログ出力。OnDeinitの設計に必要です。
送らない情報口座番号、パスワード、APIキー、Webhook URL。機密情報を保護するため。

よくある質問

OnTickにすべての処理を書いてもよいですか?

小さなEAでは動く場合もありますが、保守性は下がります。シグナル判定、発注、通知、画面更新、外部連携をOnTickに詰め込むと、原因追跡が難しくなります。定期処理はOnTimer、UI操作はOnChartEvent、取引イベント確認はOnTradeTransactionへ分ける方が安全です。

OnTimerはどのような処理に向いていますか?

価格更新に依存しない定期監視、通知、パネル更新、状態確認、外部連携、日次リセットなどに向いています。ただし、周期を短くしすぎたり、重い処理を入れすぎたりしないように注意してください。

OnTradeTransactionは何に使いますか?

注文、約定、決済、変更などの取引イベントを追跡するために使います。OrderSendやCTradeの戻り値だけでなく、実際に発生した取引イベントを確認したい場合に有効です。

OnChartEventはEAでも使えますか?

使えます。ボタン、パネル、クリック操作、キー操作などを扱う場合に使います。半裁量EAやパネルEAでは特に重要です。

OnDeinitでは何をすべきですか?

EventKillTimer、IndicatorRelease、オブジェクト削除、ファイルクローズ、終了ログ出力などを行います。OnInitで作成・設定したものは、OnDeinitで解除する考え方が基本です。

OnCalculateはEAでも使いますか?

通常、OnCalculateはインジケーターで使います。EAではOnTick、OnTimer、OnTradeTransactionなどを使います。インジケーターをEAからCopyBufferで利用する場合は、インジケーター側のOnCalculateとEA側のCopyBuffer取得の整合が重要です。

バックテストでOnChartEventは確認できますか?

ボタン操作やクリック操作のようなUIイベントは、バックテストだけでは確認しにくい場合があります。パネルEAや半裁量EAは、実チャートやデモ口座での操作確認も必要です。

関連ページ

MQL5イベント処理を整理する時は、EA設計、ログ確認、長時間稼働、チャートオブジェクト、インジケーター開発、注文処理、通知・外部連携もあわせて確認すると、実装と検証の切り分けがしやすくなります。

確認したい内容関連ページ
EA設計の責務分離を確認するMQL5 EA設計パターン完全ガイド
ログ確認とデバッグを確認するMQL5デバッグ・ログファースト開発完全ガイド
長時間稼働と安定化を確認するMQL5長時間稼働・安定化完全ガイド
チャートオブジェクトとパネルUIを確認するMQL5チャートオブジェクト・パネルUI完全ガイド
インジケーター開発とEA連携を確認するMQL5インジケーター開発・EA連携完全ガイド
EAからCopyBufferで値を取得するMQL5でインジケーター値をEAに取り込む方法
マルチシンボル・MTF対応を確認するMQL5マルチシンボル・マルチタイムフレーム完全ガイド
注文・ポジション・履歴管理を確認するMQL5注文・ポジション・履歴管理完全ガイド
CTradeを使った注文・決済を確認するMQL5標準ライブラリ・CTrade完全ガイド
OrderCheckで発注前確認をするMQL5 OrderCheckで証拠金・ロット・StopLevelを確認する方法
CSVログ出力を確認するMQL5ファイル操作・CSVログ出力完全ガイド
バックテストと最適化を確認するMT5ストラテジーテスター・最適化完全ガイド
EAログを問い合わせ前に確認するEAのログを問い合わせ前に確認する方法
開発・改修相談の入口を確認する開発・改修の相談ページ

まとめ

MQL5のイベント処理では、OnInit、OnTick、OnTimer、OnDeinit、OnTradeTransaction、OnChartEvent、OnCalculateの役割を分けることが重要です。すべてをOnTickへ詰め込むのではなく、初期化、価格更新処理、定期処理、取引イベント、UI操作、インジケーター計算、終了処理を分離すると、EAやツールの保守性が高くなります。

EAでは、OnTickをシグナル判定や発注判断の入口として使い、OnTimerで監視や通知を分け、OnTradeTransactionで注文・約定・決済イベントを追跡し、OnChartEventでパネル操作を扱うと整理しやすくなります。OnDeinitでは、タイマー解除やハンドル解放などの終了処理を忘れないようにしてください。

バックテストや不具合調査では、どのイベントで何が起きたかをログで追えるようにします。INIT、TICK、TIMER、TRADE_TX、CHART_EVENT、CALC、DEINITのようにログ名を分けておくと、EAが動かない、通知が出ない、注文結果が追えない、ボタンが反応しない、インジケーター値が取れないといった問題を切り分けやすくなります。

ABOUT ME
記事URLをコピーしました