MQL5 イベントハンドラ完全ガイド|OnInit・OnTick・OnTimer・OnChartEvent全解説

MQL5 イベントハンドラ関数を完全解説。OnInit・OnDeinit・OnTick・OnTimer・OnChartEvent・OnTrade・OnTester・OnBookEventなど、EA・インジケーター・スクリプトの動作を制御するすべてのイベント関数をコード付きで解説します。

MQL5 イベントハンドラ リファレンスカード
目次

MQL5のイベント駆動モデル

MQL5のプログラムはイベント駆動型です。特定のイベントが発生すると、対応するハンドラ関数が自動的に呼び出されます。C言語のmain()のような「最初から最後まで順に実行」するモデルとは異なります。

スクロールできます
イベントハンドラ呼び出しタイミングEAインジスクリプト
OnInit()プログラム起動時○○×
OnDeinit()プログラム終了時○○×
OnTick()新しいティック受信時○××
OnCalculate()価格データ更新時×○×
OnTimer()タイマーイベント発生時○○×
OnChartEvent()チャート操作時○○×
OnTrade()取引イベント発生時○××
OnTradeTransaction()取引トランザクション時○××
OnTester()バックテスト終了時○××
OnTesterInit()最適化開始時○××
OnTesterPass()最適化の各パス完了時○××
OnTesterDeinit()最適化終了時○××
OnBookEvent()板情報更新時○○×
OnStart()スクリプト実行時××○
アドバイス

MQL5は上から順に動くのではなく、イベントが来たら対応する関数が呼ばれる仕組みだよ。

OnInit:初期化

MQL5のOnInitが呼ばれる4つのタイミングを示した図

OnInit()はEA/インジケーターがチャートに適用されたとき、時間足が変更されたとき、inputパラメータが変更されたとき、リコンパイル時に呼び出されます。インジケーターハンドルの取得、変数の初期化、タイマー設定などを行います。

// 戻り値の型
int OnInit()
  {
   // INIT_SUCCEEDED (0):初期化成功
   // INIT_FAILED:初期化失敗(EAがチャートから削除される)
   // INIT_PARAMETERS_INCORRECT:パラメータが不正
   // INIT_AGENT_NOT_SUITABLE:テストエージェントが不適合

   return INIT_SUCCEEDED;
  }

実践的なOnInit

実践的なOnInitで行う4つの初期化手順の図
#include <TradeTrade.mqh>

CTrade trade;
int maHandle, rsiHandle;

input int    MA_Period   = 20;
input int    RSI_Period  = 14;
input double Lot         = 0.1;
input long   MagicNumber = 12345;

int OnInit()
  {
   // 1. パラメータの検証
   if(MA_Period < 1 || RSI_Period < 1)
     {
      Print("エラー: 期間は1以上を指定してください");
      return INIT_PARAMETERS_INCORRECT;
     }
   if(Lot < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
     {
      Print("エラー: ロット数が最小ロット未満です");
      return INIT_PARAMETERS_INCORRECT;
     }

   // 2. インジケーターハンドルの取得
   maHandle  = iMA(_Symbol, PERIOD_CURRENT, MA_Period, 0, MODE_EMA, PRICE_CLOSE);
   rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);

   if(maHandle == INVALID_HANDLE || rsiHandle == INVALID_HANDLE)
     {
      Print("インジケーター作成失敗");
      return INIT_FAILED;
     }

   // 3. トレードオブジェクトの設定
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(10);

   // 4. タイマー設定(必要な場合)
   EventSetTimer(60);  // 60秒ごと

   Print("EA初期化完了:", _Symbol,"", EnumToString(_Period));
   return INIT_SUCCEEDED;
  }

OnInit()でINIT_FAILEDを返すとEAはチャートから削除されます。致命的なエラー以外ではINIT_SUCCEEDEDを返し、OnTick()内でエラー処理するのが一般的です。

アドバイス

致命的なエラー以外はINIT_SUCCEEDEDを返して、エラー処理はOnTick()側でするのがコツ。

OnDeinit:終了処理

MQL5のOnDeinitで行う終了処理3つをまとめた図

OnDeinit()はEA/インジケーターが終了する直前に呼ばれます。リソースの解放、タイマーの停止、ファイルのクローズなどを行います。引数のreasonで終了理由がわかります。

void OnDeinit(const int reason)
  {
   // インジケーターハンドルの解放
   if(maHandle != INVALID_HANDLE)  IndicatorRelease(maHandle);
   if(rsiHandle != INVALID_HANDLE) IndicatorRelease(rsiHandle);

   // タイマー停止
   EventKillTimer();

   // チャートオブジェクト削除(必要な場合)
   ObjectsDeleteAll(0,"EA_");  //"EA_"で始まるオブジェクトを全削除

   // 終了理由をログに出力
   string reasons[] = {"プログラム削除",        // 0: REASON_PROGRAM"手動削除",              // 1: REASON_REMOVE"リコンパイル",          // 2: REASON_RECOMPILE"時間足/銘柄変更",       // 3: REASON_CHARTCHANGE"チャート閉鎖",          // 4: REASON_CHARTCLOSE"パラメータ変更",        // 5: REASON_PARAMETERS"別アカウント接続",      // 6: REASON_ACCOUNT"テンプレート適用",      // 7: REASON_TEMPLATE"OnInit失敗",            // 8: REASON_INITFAILED"ターミナル終了"         // 9: REASON_CLOSE
   };

   if(reason >= 0 && reason < ArraySize(reasons))
      Print("EA終了:", reasons[reason]);
   else
      Print("EA終了: reason=", reason);
  }

OnDeinit reason定数一覧

定数値意味
REASON_PROGRAM0ExpertRemove()で削除
REASON_REMOVE1チャートから手動削除
REASON_RECOMPILE2リコンパイル
REASON_CHARTCHANGE3時間足または銘柄の変更
REASON_CHARTCLOSE4チャートが閉じられた
REASON_PARAMETERS5inputパラメータの変更
REASON_ACCOUNT6別アカウントに接続
REASON_TEMPLATE7テンプレート適用
REASON_INITFAILED8OnInit()が0以外を返した
REASON_CLOSE9ターミナル終了
アドバイス

OnInit()で作ったハンドルやタイマーは、OnDeinit()で片付けるのをセットで覚えよう。

OnTick:ティックイベント

OnTickはティックが来ない時は呼ばれないことを示す図

OnTick()はEA専用のイベントハンドラで、新しいティック(価格変動)が受信されるたびに呼び出されます。EAのメインロジック(エントリー判定、ポジション管理等)はすべてこの中に書きます。

void OnTick()
  {
   // 1. 新しいバーの検出(バー確定時のみ処理する場合)
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);

   if(currentBarTime == lastBarTime)
      return;  // 同じバー内の2回目以降のティックは無視

   lastBarTime = currentBarTime;

   // 2. インジケーター値の取得
   double ma[];
   ArraySetAsSeries(ma, true);
   if(CopyBuffer(maHandle, 0, 0, 3, ma) < 3) return;

   // 3. エントリー条件の判定
   // ...

   // 4. ポジション管理
   // ...
  }
重要

OnTick()はティックが来ない限り呼ばれません。週末やメンテナンス中は一切実行されません。時間に基づく処理が必要な場合はOnTimer()を併用してください。

新しいバーの検出パターン

新しいバーの検出パターン2つを比べた図
// 方法1: iTime()で比較(シンプル)
bool IsNewBar()
  {
   static datetime lastBar = 0;
   datetime current = iTime(_Symbol, PERIOD_CURRENT, 0);

   if(current != lastBar)
     {
      lastBar = current;
      return true;
     }
   return false;
  }

// 方法2: iBars()で比較(より軽量)
bool IsNewBar2()
  {
   static int lastBars = 0;
   int current = iBars(_Symbol, PERIOD_CURRENT);

   if(current != lastBars)
     {
      lastBars = current;
      return true;
     }
   return false;
  }

// 使用例
void OnTick()
  {
   if(!IsNewBar()) return;

   // ここはバー確定時のみ実行される
   Print("新しいバー:", iTime(_Symbol, PERIOD_CURRENT, 0));
  }

バー確定時のみ処理することで、EAの負荷を大幅に軽減できます。1時間足EAなら1時間に1回しか判定しないため、バックテストも高速化されます。トレーリングストップのようにティックごとに処理が必要な部分は、新バー判定の外に書きます。

アドバイス

ティックが来ないと動かないので、時間で動かしたい処理はOnTimer()に任せよう。

OnTimer:タイマーイベント

関数単位使いどころ
EventSetTimer()秒60秒ごとなどの定期処理
EventSetMillisecondTimer()ミリ秒より精密な間隔(100ms以下は避ける)
EventKillTimer()—OnDeinit()でタイマーを停止
タイマー関数の使い分け

OnTimer()はタイマーイベントで定期的に呼び出されます。ティックに依存しない定期処理(パネル更新、ログ記録、ニュース監視等)に使用します。

int OnInit()
  {
   // 秒単位でタイマー設定
   EventSetTimer(60);        // 60秒ごと

   // ミリ秒単位(より精密)
   // EventSetMillisecondTimer(500);  // 500msごと

   return INIT_SUCCEEDED;
  }

void OnTimer()
  {
   // 60秒ごとに呼ばれる
   Print("Timer:", TimeCurrent());

   // ダッシュボード更新
   UpdateDashboard();

   // 日次レポート(特定時刻に処理)
   MqlDateTime dt;
   TimeCurrent(dt);
   if(dt.hour == 23 && dt.min == 59)
      GenerateDailyReport();
  }

void OnDeinit(const int reason)
  {
   EventKillTimer();  // タイマー停止(必須)
  }

EventSetMillisecondTimer()を短い間隔で設定するとCPU負荷が高くなります。100ms以下は避けてください。また、タイマーは1つのプログラムにつき1つだけです。2回目のEventSetTimer()は前の設定を上書きします。

アドバイス

タイマーは1つのプログラムに1つだけ。EventSetTimer()を2回呼ぶと上書きになるよ。

OnChartEvent:チャートイベント

OnChartEventが受け取る4つの引数を示した図

OnChartEvent()はチャート上でユーザーが操作を行ったときに呼ばれます。ボタンクリック、キーボード入力、マウス移動、オブジェクトのドラッグ等を検知できます。EA上にGUIパネルを作る場合に必須です。

void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   // ボタンクリック
   if(id == CHARTEVENT_OBJECT_CLICK)
     {
      if(sparam =="btnBuy")
        {
         Print("買いボタンがクリックされました");
         // ボタン状態をリセット
         ObjectSetInteger(0,"btnBuy", OBJPROP_STATE, false);
        }
      if(sparam =="btnSell")
        {
         Print("売りボタンがクリックされました");
         ObjectSetInteger(0,"btnSell", OBJPROP_STATE, false);
        }
     }

   // キーボード入力
   if(id == CHARTEVENT_KEYDOWN)
     {
      // lparamにキーコードが入る
      if(lparam == 'B')
         Print("Bキーが押されました");
      if(lparam == 27)  // ESCキー
         Print("ESCキーが押されました");
     }

   // マウスクリック
   if(id == CHARTEVENT_CLICK)
     {
      int x = (int)lparam;
      int y = (int)dparam;
      Print("クリック位置: x=", x," y=", y);

      // ピクセル座標を時間・価格に変換
      datetime time;
      double   price;
      int      window;
      if(ChartXYToTimePrice(0, x, y, window, time, price))
         Print("時間=", time," 価格=", price);
     }

   // カスタムイベント(EventChartCustom()で送信)
   if(id >= CHARTEVENT_CUSTOM)
     {
      int customId = id - CHARTEVENT_CUSTOM;
      Print("カスタムイベント: id=", customId,"", sparam);
     }
  }

CHARTEVENT定数一覧

スクロールできます
定数意味lparamdparamsparam
CHARTEVENT_KEYDOWNキー押下キーコード繰り返し回数キー状態
CHARTEVENT_MOUSE_MOVEマウス移動X座標Y座標フラグ
CHARTEVENT_OBJECT_CREATEオブジェクト作成––オブジェクト名
CHARTEVENT_OBJECT_CHANGEオブジェクト変更––オブジェクト名
CHARTEVENT_OBJECT_DELETEオブジェクト削除––オブジェクト名
CHARTEVENT_CLICKチャートクリックX座標Y座標–
CHARTEVENT_OBJECT_CLICKオブジェクトクリックX座標Y座標オブジェクト名
CHARTEVENT_OBJECT_DRAGオブジェクト移動––オブジェクト名
CHARTEVENT_OBJECT_ENDEDITテキスト編集完了––オブジェクト名
CHARTEVENT_CHART_CHANGEチャートプロパティ変更–––
CHARTEVENT_CUSTOMカスタムイベント任意任意任意

ボタン付きパネルの作成例

OnChartEventでボタン付きパネルを動かす3つの手順の図
#include <TradeTrade.mqh>
CTrade trade;

void CreatePanel()
  {
   // 買いボタン
   ObjectCreate(0,"btnBuy", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0,"btnBuy", OBJPROP_XDISTANCE, 20);
   ObjectSetInteger(0,"btnBuy", OBJPROP_YDISTANCE, 30);
   ObjectSetInteger(0,"btnBuy", OBJPROP_XSIZE, 100);
   ObjectSetInteger(0,"btnBuy", OBJPROP_YSIZE, 30);
   ObjectSetString(0,"btnBuy", OBJPROP_TEXT,"BUY");
   ObjectSetInteger(0,"btnBuy", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0,"btnBuy", OBJPROP_BGCOLOR, clrDodgerBlue);
   ObjectSetInteger(0,"btnBuy", OBJPROP_FONTSIZE, 10);

   // 売りボタン
   ObjectCreate(0,"btnSell", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0,"btnSell", OBJPROP_XDISTANCE, 130);
   ObjectSetInteger(0,"btnSell", OBJPROP_YDISTANCE, 30);
   ObjectSetInteger(0,"btnSell", OBJPROP_XSIZE, 100);
   ObjectSetInteger(0,"btnSell", OBJPROP_YSIZE, 30);
   ObjectSetString(0,"btnSell", OBJPROP_TEXT,"SELL");
   ObjectSetInteger(0,"btnSell", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0,"btnSell", OBJPROP_BGCOLOR, clrCrimson);
   ObjectSetInteger(0,"btnSell", OBJPROP_FONTSIZE, 10);

   // 全決済ボタン
   ObjectCreate(0,"btnCloseAll", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0,"btnCloseAll", OBJPROP_XDISTANCE, 240);
   ObjectSetInteger(0,"btnCloseAll", OBJPROP_YDISTANCE, 30);
   ObjectSetInteger(0,"btnCloseAll", OBJPROP_XSIZE, 100);
   ObjectSetInteger(0,"btnCloseAll", OBJPROP_YSIZE, 30);
   ObjectSetString(0,"btnCloseAll", OBJPROP_TEXT,"CLOSE ALL");
   ObjectSetInteger(0,"btnCloseAll", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0,"btnCloseAll", OBJPROP_BGCOLOR, clrDimGray);

   ChartRedraw();
  }

void OnChartEvent(const int id, const long &lparam,
                  const double &dparam, const string &sparam)
  {
   if(id != CHARTEVENT_OBJECT_CLICK) return;

   if(sparam =="btnBuy")
     {
      trade.Buy(0.1, _Symbol);
      ObjectSetInteger(0,"btnBuy", OBJPROP_STATE, false);
     }
   else if(sparam =="btnSell")
     {
      trade.Sell(0.1, _Symbol);
      ObjectSetInteger(0,"btnSell", OBJPROP_STATE, false);
     }
   else if(sparam =="btnCloseAll")
     {
      for(int i = PositionsTotal() - 1; i >= 0; i--)
        {
         ulong ticket = PositionGetTicket(i);
         if(PositionGetString(POSITION_SYMBOL) == _Symbol)
            trade.PositionClose(ticket);
        }
      ObjectSetInteger(0,"btnCloseAll", OBJPROP_STATE, false);
     }
  }
アドバイス

まずidでイベントの種類を分けて、sparamのオブジェクト名でボタンを見分けよう。

OnTrade:取引イベント

OnTradeとOnTradeTransactionの選び方を示した図

OnTrade()は注文の発注・変更・削除、ポジションのオープン・クローズ・変更など、取引に関するイベントが発生したときに呼ばれます。OnTradeTransaction()よりシンプルですが、詳細な情報は取得できません。

void OnTrade()
  {
   // ポジション数の変化を検知
   static int lastPositions = 0;
   int currentPositions = PositionsTotal();

   if(currentPositions > lastPositions)
      Print("新しいポジションがオープンしました 現在:", currentPositions);
   else if(currentPositions < lastPositions)
      Print("ポジションがクローズされました 現在:", currentPositions);

   lastPositions = currentPositions;

   // 待機注文の変化を検知
   static int lastOrders = 0;
   int currentOrders = OrdersTotal();

   if(currentOrders != lastOrders)
     {
      Print("待機注文数が変化:", lastOrders," →", currentOrders);
      lastOrders = currentOrders;
     }
  }

OnTrade()とOnTradeTransaction()は同じ取引イベントに対して両方呼ばれます。詳細な約定情報が必要ならOnTradeTransaction()、単にポジション数の変化だけを見るならOnTrade()が適しています。

アドバイス

1回の取引で、OnTradeTransaction()は何度も呼ばれて、そのあとにOnTrade()が呼ばれるよ。回数を数えるなら注意してね。

OnCalculate:インジケーター計算

OnCalculateの2つのオーバーロードを比べた図

OnCalculate()はカスタムインジケーター専用のイベントハンドラです。価格データが更新されるたびに呼ばれ、インジケーターバッファに値を格納します。2つのオーバーロードがあります。

// 形式1: OHLCVデータを受け取る(一般的)
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[])
  {
   // 最初の呼び出しではprev_calculated=0(全バー計算)
   // 2回目以降はprev_calculated=rates_total-1(最新バーのみ再計算)
   int start = (prev_calculated == 0) ? 0 : prev_calculated - 1;

   for(int i = start; i < rates_total; i++)
     {
      // ここでインジケーターバッファに値をセット
      // Buffer[i] = ... ;
     }

   return rates_total;  // 次回のprev_calculatedに渡される
  }

// 形式2: 単一のデータ配列を受け取る(他のインジケーターに適用する場合)
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,            // 有効データの開始位置
                const double &price[])       // 適用価格データ
  {
   int start = (prev_calculated == 0) ? begin : prev_calculated - 1;

   for(int i = start; i < rates_total; i++)
     {
      // price[i] は選択された適用価格
      // Buffer[i] = ... ;
     }

   return rates_total;
  }

カスタムSMAインジケーターの完全例

カスタムSMAインジケーターの計算手順3つの図
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_label1"My SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2

input int Period = 20;

double SMA_Buffer[];

int OnInit()
  {
   SetIndexBuffer(0, SMA_Buffer, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, Period - 1);
   IndicatorSetString(INDICATOR_SHORTNAME,"MySMA(" + IntegerToString(Period) +")");
   return INIT_SUCCEEDED;
  }

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   if(rates_total < Period) return 0;

   int start;
   if(prev_calculated == 0)
     {
      start = begin + Period - 1;
      // 最初のSMAを計算
      double sum = 0;
      for(int i = begin; i < start; i++)
         sum += price[i];
      sum += price[start];
      SMA_Buffer[start] = sum / Period;
      start++;
     }
   else
      start = prev_calculated - 1;

   // 逐次計算(前のSMAから差分で計算、高速)
   for(int i = start; i < rates_total; i++)
      SMA_Buffer[i] = SMA_Buffer[i - 1] + (price[i] - price[i - Period]) / Period;

   return rates_total;
  }
アドバイス

戻り値のrates_totalが次回のprev_calculatedになる。差分計算の要だよ。

OnTester:バックテスト終了時

OnTesterの戻り値が最適化の基準になることを示す図

OnTester()はストラテジーテスターでのバックテストが完了した直後に呼ばれます。カスタム最適化基準を返すことができます。

double OnTester()
  {
   // テスト結果の統計を取得
   double profit     = TesterStatistics(STAT_PROFIT);
   double maxDD      = TesterStatistics(STAT_EQUITY_DDREL_PERCENT);
   int    totalTrades = (int)TesterStatistics(STAT_TRADES);
   double profitFactor = TesterStatistics(STAT_PROFIT_FACTOR);
   double sharpe     = TesterStatistics(STAT_SHARPE_RATIO);

   Print("=== バックテスト結果 ===");
   Print("純利益:", profit);
   Print("最大DD:", maxDD,"%");
   Print("取引数:", totalTrades);
   Print("PF:", profitFactor);
   Print("シャープ:", sharpe);

   // カスタム最適化基準を返す
   // 例: 利益をDDで割ったリカバリーファクター
   if(maxDD == 0) return 0;
   if(totalTrades < 30) return 0;  // 取引数が少なすぎる場合は0

   double recoveryFactor = profit / (maxDD / 100.0 * TesterStatistics(STAT_INITIAL_DEPOSIT));

   return recoveryFactor;  // この値が最大化される
  }

OnTester()の戻り値は、最適化で「カスタム最大」を選択した場合の最適化基準になります。リカバリーファクター、シャープレシオ、プロフィットファクターなど、自分の重視する指標を返してください。

アドバイス

OnTester()の戻り値は、最適化で「カスタム最大」を選んだ時の基準になるよ。

OnTesterInit / OnTesterPass / OnTesterDeinit:最適化制御

最適化の前後に呼ばれる3つのイベントハンドラの図

最適化の前後に呼ばれるイベントハンドラです。最適化の全体管理、各パスの結果収集、フレーム処理に使用します。

// 最適化開始前(メインチャートのEAで呼ばれる)
void OnTesterInit()
  {
   Print("最適化を開始します...");
  }

// 各最適化パスが完了するたびに呼ばれる
void OnTesterPass()
  {
   // FrameFirst() / FrameNext() でフレームデータを読み取る
   ulong  pass;
   string name;
   long   id;
   double value;

   if(FrameFirst())
     {
      FrameInputs(name, pass);
      FrameNext(pass, name, id, value);
      PrintFormat("パス #%d: %s = %.2f", pass, name, value);
     }
  }

// 最適化完了後
void OnTesterDeinit()
  {
   Print("最適化が完了しました");
  }

// テスターのOnTester()内でフレームを送信
double OnTester()
  {
   double result = TesterStatistics(STAT_PROFIT);

   // フレームとしてメインチャートに送信
   FrameAdd("Profit", 1, result, NULL);

   return result;
  }
アドバイス

最適化の開始・各パスの完了・終了の3か所で呼ばれる。結果の集計に使えるよ。

OnBookEvent:板情報イベント

OnBookEventで板情報を扱う3つの手順の図

OnBookEvent()は板情報(Depth of Market / DOM)が更新されたときに呼ばれます。事前にMarketBookAdd()で購読を開始する必要があります。

int OnInit()
  {
   // 板情報の購読を開始
   if(!MarketBookAdd(_Symbol))
      Print("板情報の購読に失敗:", GetLastError());

   return INIT_SUCCEEDED;
  }

void OnBookEvent(const string &symbol)
  {
   if(symbol != _Symbol) return;

   MqlBookInfo book[];
   if(MarketBookGet(_Symbol, book))
     {
      for(int i = 0; i < ArraySize(book); i++)
        {
         PrintFormat("Type=%s Price=%.5f Volume=%d",
                     (book[i].type == BOOK_TYPE_SELL) ?"SELL" :"BUY",
                     book[i].price, (int)book[i].volume);
        }
     }
  }

void OnDeinit(const int reason)
  {
   MarketBookRelease(_Symbol);  // 購読解除
  }
アドバイス

MarketBookAdd()で購読したら、終了時はMarketBookRelease()で解除しよう。

OnStart:スクリプト実行

スクリプトのOnStartは1回だけ実行されることを示す図

OnStart()はスクリプト専用のイベントハンドラです。スクリプトはチャートに適用されるとOnStart()が1回だけ実行され、完了後に自動的に削除されます。

// 全ポジション一括決済スクリプト
#include <TradeTrade.mqh>

void OnStart()
  {
   CTrade trade;
   int closed = 0;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;

      if(trade.PositionClose(ticket))
         closed++;
      else
         Print("決済失敗: #", ticket," Error=", GetLastError());
     }

   Print("決済完了:", closed,"ポジション");
   // スクリプトはここで自動的に終了・削除される
  }
アドバイス

スクリプトはOnStart()を1回実行して終わり。動かし続けたい処理はEAで書こう。

カスタムイベント(EventChartCustom)

EventChartCustomでカスタムイベントを送受信する手順の図

EventChartCustom()を使うと、自分でイベントを発生させてOnChartEvent()で受け取ることができます。EAの異なるモジュール間の通信や、非同期処理の通知に使えます。

// カスタムイベントIDの定義
#define EVENT_TRADE_OPENED   0
#define EVENT_TRADE_CLOSED   1
#define EVENT_SL_HIT         2

// イベントを発火
void NotifyTradeOpened(ulong ticket, double price)
  {
   EventChartCustom(0,                    // チャートID(0=自分のチャート)
                    EVENT_TRADE_OPENED,    // カスタムイベントID
                    (long)ticket,          // lparam
                    price,                 // dparam"Trade Opened"         // sparam
                    );
  }

// イベントを受信
void OnChartEvent(const int id, const long &lparam,
                  const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_CUSTOM + EVENT_TRADE_OPENED)
     {
      Print("取引オープン通知: ticket=", lparam," price=", dparam);
      // ここでGUI更新、ログ記録等を行う
     }
   else if(id == CHARTEVENT_CUSTOM + EVENT_TRADE_CLOSED)
     {
      Print("取引クローズ通知: ticket=", lparam," PL=", dparam);
     }
  }
アドバイス

受け取る側では、CHARTEVENT_CUSTOMに自分で決めたIDを足した値で判定するよ。

イベントの実行順序

MQL5のイベントハンドラがシングルスレッドで順に実行されることを示す図

MQL5公式ドキュメントでは、イベントの処理は次のように決まっています。ハンドラ同士に「OnTick()が最優先」といった優先順位表はなく、届いた順に処理されます。

  • プログラム(EA・インジケーター・スクリプト)ごとに専用のイベントキューがあり、届いた順に処理される
  • OnTick()の実行中に届いた新しいティックは、キューに溜まらず捨てられる
  • Timerイベントは、すでにキューにある間や処理中の間は新しく追加されない
  • EAやインジケーターは、チャートごとに別のスレッドで動く(1本のEA自身の中ではハンドラは1つずつ順に実行される)

1本のEAのイベントハンドラはシングルスレッドで実行されます。OnTick()の処理中にOnTimer()が呼ばれることはありません。OnTick()の処理に時間がかかると、その間に届いた新しいティックは捨てられます(キューには溜まりません)。タイマーイベントも、すでにキューにある間は追加されません。処理は軽く保ちましょう。

プロが開発したEAをお探しの方は → シストレ.COM EA一覧

アドバイス

ハンドラは1つずつ順番に動く。OnTick()を重くすると他のイベントが待たされるよ。

まとめ

MQL5のイベントハンドラはMQL4より豊富で、OnTrade()とOnTradeTransaction()で取引イベントを細かく制御できます。OnTimer()はEventSetTimer()で有効化し、必ずOnDeinit()でEventKillTimer()を呼びましょう。OnChartEvent()はカスタムイベントを使ってEA間通信も実装できます。OnTimer()の中で最新の価格が要るときは、SymbolInfoTick()で取得できます(MQL4のRefreshRates()はMQL5にありません)。

よくある質問

MQL5のイベントハンドラの種類は?

OnInit()/OnDeinit()/OnTick()はMQL4と同じですが、MQL5ではOnTrade()(取引イベント)、OnTradeTransaction()(取引トランザクション詳細)、OnBookEvent()(板情報)、OnTesterInit()/OnTesterPass()/OnTesterDeinit()(最適化の制御)が追加されました。

OnTrade()とOnTradeTransaction()の違いは?

OnTrade()は取引サーバーでの取引操作が完了したときに呼ばれ、OnTradeTransaction()は個々の取引操作(注文送信、約定等)ごとに詳細情報付きで呼ばれます。1回の取引でOnTradeTransaction()は複数回呼ばれ、そのあとにOnTrade()が呼ばれます。より詳細な制御にはOnTradeTransaction()を使います。

MQL5のOnTick()の注意点は?

MQL5のOnTick()はMQL4と同様です。1本のEAのハンドラは1つずつ順に実行され、OnTick()の実行中に届いた新しいティックは捨てられるため、処理は軽く保ちます。インジケーターハンドルはOnInit()で取得・保持し、OnTick()内でのIndicatorCreate()は避けましょう。

OnTimer()の高精度版はありますか?

EventSetMillisecondTimer()でミリ秒単位のタイマーを設定できます。高頻度の監視処理やリアルタイムダッシュボードの更新に使います。CPU負荷が高くなるため注意が必要で、OnDeinit()でEventKillTimer()を呼ぶのを忘れずに。

OnChartEvent()はMQL4と同じですか?

基本的な使い方はMQL4と同じで、カスタムイベント(CHARTEVENT_CUSTOM)もEventChartCustom()もMQL4にあります。EA間通信やインジケーター連携に使えます。EventChartCustom()でカスタムイベントを他のEAに送信できます。

OnDeinit()で必ず実行すべき処理は?

IndicatorRelease()でインジケーターハンドルを解放、EventKillTimer()でタイマーを停止、ObjectsDeleteAll()でオブジェクトを削除、FileClose()でファイルハンドルを閉じることが必須です。リソースリークを防ぐために徹底しましょう。

EAのログ出力やアラート通知、型変換といった基盤部分の関数は、MQL5 共通関数・変換関数の完全ガイドにまとめています。

ロット計算に必要な口座残高や証拠金、銘柄ごとの取引条件を取得する関数は、MQL5 口座情報・マーケット情報の完全ガイドで扱っています。

MQL5リファレンス 関連記事

シストレ.COM編集部|200種類以上のEAをフォワード計測する運営3年の検証メディア。MetaTrader (MT4/MT5) を実運用してきた編集部が監修・執筆しています。

⚠ リスクに関する注意事項

本記事は情報提供を目的としており、特定の金融商品の売買を推奨するものではありません。FX取引はレバレッジ取引の特性上、預託した証拠金以上の損失が生じる可能性があります。取引の際は、ご自身の判断と責任において行ってください。詳しくは特定商取引法に基づく表記をご確認ください。

\シストレ.COMで実績のあるEAが使い放題!/
無料会員登録はこちら
FX自動売買でEAを探すなら
シストレ.COM 記事下バナー
実績あるEAが無料
厳格な審査を乗り越えた実績のあるEAが無料で使えます!
自由な口座選び
有料版を購入し柔軟な口座選びが可能!
フォワードテスト公開
全EAのフォワードテスト結果を公開中!
FX初心者も安心
初心者の方も安心して取引を始められます。
多様なEA選択肢
様々な種別のEAをご用意!自分の手法にあった取引が可能です。
信頼のFX会社と提携
人気のFX会社と提携中!様々なFX会社から選べます。
\1分で登録完了!EA探すならシストレ.COM!/
無料会員登録はこちら

この記事が気に入ったら
フォローしてね!

お役立ち情報をシェアする
  • URLをコピーしました!

この記事を書いた人

シストレ.COM編集部は、FX自動売買(EA)とテクニカル分析を専門とするインターネットメディア「シストレ.COM」の編集チームです。200本以上のEAのフォワードテスト結果を公開し、実際の運用データに基づいた客観的な情報を発信しています。記事の執筆・監修はMetaTrader(MT4/MT5)での実運用経験を持つスタッフが担当。

目次