【MQL4関数】OnDeinit関数とは?アンイニシャルコード発生時に実行されるイベント関数

関数

OnDeinit関数とは?

OnDeinit関数は、Expert Advisor(EA)やカスタムインジケーターがチャートから削除されるとき、またはアンイニシャライズ(非初期化)イベントが発生したときに自動的に呼び出されるイベント関数です。

プログラムの「後片付け」を行うための関数であり、リソースの解放やファイルのクローズ、オブジェクトの削除など、プログラム終了時に必要な処理を記述します。

基本的な書式

void OnDeinit(const int reason)
{
   // 終了時の処理をここに記述
}

引数

引数名 説明
reason const int アンイニシャライズの理由コード(なぜOnDeinitが呼ばれたかを示す)

戻り値

戻り値はありません(void型)。

アンイニシャライズ理由コード(reason)一覧

OnDeinit関数の引数reasonには、以下の定数が渡されます。これにより、なぜプログラムが終了するのかを判別できます。

定数名 説明
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 ターミナルが閉じられた

プログラム例1:基本的なOnDeinit関数の使い方

最もシンプルな例として、OnDeinitが呼ばれた理由をログに出力する基本パターンです。

//+------------------------------------------------------------------+
//| Expert Advisor - OnDeinit基本例                                    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("EAが初期化されました");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // メイン処理(ここでは省略)
}

//+------------------------------------------------------------------+
//| 終了処理関数                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 終了理由をログに出力する
   Print("EAが終了しました。理由コード: ", reason);
   
   // 理由コードに応じたメッセージを表示
   switch(reason)
   {
      case REASON_PROGRAM:
         Print("→ ExpertRemove()による終了");
         break;
      case REASON_REMOVE:
         Print("→ チャートからEAが削除されました");
         break;
      case REASON_RECOMPILE:
         Print("→ プログラムが再コンパイルされました");
         break;
      case REASON_CHARTCHANGE:
         Print("→ 通貨ペアまたは時間足が変更されました");
         break;
      case REASON_CHARTCLOSE:
         Print("→ チャートが閉じられました");
         break;
      case REASON_PARAMETERS:
         Print("→ パラメータが変更されました");
         break;
      case REASON_ACCOUNT:
         Print("→ アカウントが変更されました");
         break;
      case REASON_TEMPLATE:
         Print("→ テンプレートが適用されました");
         break;
      case REASON_INITFAILED:
         Print("→ 初期化に失敗しました");
         break;
      case REASON_CLOSE:
         Print("→ ターミナルが閉じられました");
         break;
      default:
         Print("→ 不明な理由コード");
         break;
   }
}

このコードでは、switch文を使ってすべてのアンイニシャライズ理由を判別し、それぞれに対応するメッセージをエキスパートログに出力しています。デバッグ時に非常に役立つ基本パターンです。

プログラム例2:チャートオブジェクトの後片付け

EAやインジケーターがチャート上にライン・ラベル・矢印などのオブジェクトを描画した場合、OnDeinitで確実に削除しないとチャートにゴミが残ってしまいます。

//+------------------------------------------------------------------+
//| Expert Advisor - オブジェクト削除の例                                |
//+------------------------------------------------------------------+
#define PREFIX "MyEA_"  // オブジェクト名のプレフィックス

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // 水平ラインを作成
   string lineName = PREFIX + "HLine";
   ObjectCreate(0, lineName, OBJ_HLINE, 0, 0, Ask);
   ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrRed);
   ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 2);
   
   // テキストラベルを作成
   string labelName = PREFIX + "Label";
   ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 20);
   ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 30);
   ObjectSetString(0, labelName, OBJPROP_TEXT, "EA稼働中");
   ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 12);
   
   // 矢印オブジェクトを作成
   string arrowName = PREFIX + "Arrow";
   ObjectCreate(0, arrowName, OBJ_ARROW, 0, TimeCurrent(), Bid);
   ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, 233);
   ObjectSetInteger(0, arrowName, OBJPROP_COLOR, clrLime);
   
   Print("オブジェクトを3個作成しました");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // メイン処理(ここでは省略)
}

//+------------------------------------------------------------------+
//| 終了処理関数 - オブジェクトの後片付け                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // プレフィックスが一致するオブジェクトをすべて検索して削除
   int totalObjects = ObjectsTotal(0);
   int deletedCount = 0;
   
   // 後ろから順にループする(削除時のインデックスずれを防ぐ)
   for(int i = totalObjects - 1; i >= 0; i--)
   {
      string objName = ObjectName(0, i);
      
      // プレフィックスが一致するオブジェクトのみ削除
      if(StringFind(objName, PREFIX) == 0)
      {
         ObjectDelete(0, objName);
         deletedCount++;
      }
   }
   
   Print("OnDeinit: ", deletedCount, "個のオブジェクトを削除しました");
   
   // チャートを再描画して反映
   ChartRedraw(0);
}

ポイントは、オブジェクト名に共通のプレフィックスをつけておくことです。OnDeinitではプレフィックスが一致するオブジェクトだけを削除するため、他のEAやインジケーターが作成したオブジェクトを誤って消してしまう心配がありません。また、オブジェクトをループで削除する際は、インデックスがずれないよう後ろから前に向かってループする点も重要です。

プログラム例3:ファイルハンドルの安全なクローズ

EAでログファイルやCSVファイルに書き込んでいる場合、OnDeinitでファイルを確実に閉じる必要があります。ファイルを閉じないまま終了すると、データが破損する可能性があります。

//+------------------------------------------------------------------+
//| Expert Advisor - ファイルクローズの例                                |
//+------------------------------------------------------------------+
int gFileHandle = INVALID_HANDLE;  // グローバル変数でファイルハンドルを保持

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // ログファイルを開く(追記モード)
   string fileName = "TradeLog_" + Symbol() + ".csv";
   gFileHandle = FileOpen(fileName, FILE_WRITE | FILE_CSV | FILE_COMMON, ',');
   
   if(gFileHandle == INVALID_HANDLE)
   {
      Print("エラー: ファイルを開けませんでした。エラーコード: ", GetLastError());
      return(INIT_FAILED);  // 初期化失敗 → OnDeinitがREASON_INITFAILEDで呼ばれる
   }
   
   // ヘッダー行を書き込む
   FileWrite(gFileHandle, "日時", "種別", "通貨ペア", "ロット", "価格", "備考");
   Print("ログファイルを開きました: ", fileName);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // 例:何らかの条件でトレード情報をファイルに書き込む
   static datetime lastWriteTime = 0;
   
   // 1分に1回だけ書き込む例
   if(TimeCurrent() - lastWriteTime >= 60)
   {
      if(gFileHandle != INVALID_HANDLE)
      {
         FileWrite(gFileHandle,
                   TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS),
                   "INFO",
                   Symbol(),
                   "0.00",
                   DoubleToString(Bid, Digits),
                   "定期記録");
      }
      lastWriteTime = TimeCurrent();
   }
}

//+------------------------------------------------------------------+
//| 終了処理関数 - ファイルの安全なクローズ                               |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // ファイルハンドルが有効な場合のみクローズする
   if(gFileHandle != INVALID_HANDLE)
   {
      // 最終行として終了情報を書き込む
      FileWrite(gFileHandle,
                TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS),
                "DEINIT",
                Symbol(),
                "",
                "",
                "終了理由: " + IntegerToString(reason));
      
      // ファイルをフラッシュ(バッファの内容をディスクに書き出す)
      FileFlush(gFileHandle);
      
      // ファイルを閉じる
      FileClose(gFileHandle);
      gFileHandle = INVALID_HANDLE;  // ハンドルを無効値に戻す
      
      Print("ログファイルを正常にクローズしました");
   }
   else
   {
      Print("ファイルハンドルは無効でした(クローズ不要)");
   }
}

ファイル操作で重要なのは、FileClose()の前にFileFlush()を呼んでバッファの内容を確実にディスクに書き出すことと、クローズ後にハンドル変数をINVALID_HANDLEにリセットすることです。これにより、万が一OnDeinitが複数回呼ばれるような状況でも安全に動作します。

プログラム例4:理由コードに応じた条件分岐処理

実践的なEAでは、終了理由によって処理を分けたいケースがあります。例えば、パラメータ変更時はポジション情報を保持し、完全な終了時のみグローバル変数をクリアする、といった使い分けです。

//+------------------------------------------------------------------+
//| Expert Advisor - 理由コードに応じた条件分岐                          |
//+------------------------------------------------------------------+
#define EA_NAME   "MyTradingEA"
#define PREFIX    EA_NAME + "_"

input double Lots         = 0.1;   // ロット数
input int    MagicNumber  = 12345; // マジックナンバー

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // グローバル変数から前回の状態を復元(パラメータ変更・再コンパイル時)
   string gvName = PREFIX + "LastTicket";
   if(GlobalVariableCheck(gvName))
   {
      int lastTicket = (int)GlobalVariableGet(gvName);
      Print("前回のチケット番号を復元: ", lastTicket);
   }
   
   // タイマーを設定(1秒間隔)
   EventSetTimer(1);
   
   Print(EA_NAME, " が初期化されました");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // メインのトレードロジック(ここでは省略)
}

//+------------------------------------------------------------------+
//| タイマーイベント関数                                                 |
//+------------------------------------------------------------------+
void OnTimer()
{
   // 定期処理(ここでは省略)
}

//+------------------------------------------------------------------+
//| 終了処理関数 - 理由コードに応じた条件分岐                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // ------------------------------------
   // 1. すべての終了理由で共通の処理
   // ------------------------------------
   
   // タイマーを停止
   EventKillTimer();
   Print("タイマーを停止しました");
   
   // ------------------------------------
   // 2. 一時的な終了の場合(状態を保持)
   // ------------------------------------
   if(reason == REASON_PARAMETERS  ||   // パラメータ変更
      reason == REASON_RECOMPILE   ||   // 再コンパイル
      reason == REASON_CHARTCHANGE ||   // 通貨ペア・時間足変更
      reason == REASON_TEMPLATE)        // テンプレート変更
   {
      Print("一時的な終了です。状態情報を保持します。");
      
      // 現在のポジション情報をグローバル変数に保存
      SaveCurrentState();
      
      // チャートオブジェクトは削除しない(再起動後にそのまま使うため)
      return;
   }
   
   // ------------------------------------
   // 3. 完全な終了の場合(クリーンアップ)
   // ------------------------------------
   if(reason == REASON_REMOVE     ||   // EA削除
      reason == REASON_CHARTCLOSE ||   // チャート閉じ
      reason == REASON_CLOSE      ||   // ターミナル閉じ
      reason == REASON_PROGRAM    ||   // ExpertRemove()
      reason == REASON_ACCOUNT)        // アカウント変更
   {
      Print("完全な終了です。フルクリーンアップを実行します。");
      
      // チャート上のすべての自作オブジェクトを削除
      DeleteAllObjects();
      
      // グローバル変数を削除
      CleanupGlobalVariables();
      
      // チャートのコメントをクリア
      Comment("");
      
      Print(EA_NAME, " のクリーンアップが完了しました");
      return;
   }
   
   // ------------------------------------
   // 4. 初期化失敗の場合
   // ------------------------------------
   if(reason == REASON_INITFAILED)
   {
      Print("初期化に失敗したため終了します");
      // 最小限のクリーンアップのみ実行
      DeleteAllObjects();
      Comment("");
      return;
   }
}

//+------------------------------------------------------------------+
//| 現在の状態をグローバル変数に保存する関数                               |
//+------------------------------------------------------------------+
void SaveCurrentState()
{
   // 保有ポジションのチケット番号を保存
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
         {
            string gvName = PREFIX + "LastTicket";
            GlobalVariableSet(gvName, OrderTicket());
            Print("チケット番号 ", OrderTicket(), " をグローバル変数に保存しました");
            break;
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 自作オブジェクトをすべて削除する関数                                   |
//+------------------------------------------------------------------+
void DeleteAllObjects()
{
   int count = 0;
   for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
   {
      string objName = ObjectName(0, i);
      if(StringFind(objName, PREFIX) == 0)
      {
         ObjectDelete(0, objName);
         count++;
      }
   }
   if(count > 0)
   {
      Print(count, "個のオブジェクトを削除しました");
      ChartRedraw(0);
   }
}

//+------------------------------------------------------------------+
//| グローバル変数をクリーンアップする関数                                 |
//+------------------------------------------------------------------+
void CleanupGlobalVariables()
{
   int count = 0;
   int total = GlobalVariablesTotal();
   
   // 後ろからループして削除
   for(int i = total - 1; i >= 0; i--)
   {
      string gvName = GlobalVariableName(i);
      if(StringFind(gvName, PREFIX) == 0)
      {
         GlobalVariableDel(gvName);
         count++;
      }
   }
   
   if(count > 0)
      Print(count, "個のグローバル変数を削除しました");
}

この例では、終了理由を3つのカテゴリ(一時的な終了・完全な終了・初期化失敗)に分類し、それぞれ異なる処理を行っています。パラメータ変更や再コンパイル時にはオブジェクトやグローバル変数を保持し、EA削除やチャートクローズ時には完全なクリーンアップを実行します。

よくある使い方のポイント・注意事項

1. OnDeinitの実行時間には制限がある

OnDeinit関数の実行には約2.5秒のタイムリミットがあります。時間のかかる処理(大量のファイル操作やネットワーク通信など)をOnDeinitに入れると、処理が途中で打ち切られる可能性があります。

2. パラメータ変更時にもOnDeinitは呼ばれる

EAのプロパティ画面でパラメータを変更して「OK」を押すと、OnDeinit → OnInit の順で呼ばれます。このとき理由コードはREASON_PARAMETERS(5)です。パラメータ変更のたびにオブジェクトが消えてしまう問題を防ぐために、理由コードによる条件分岐が有効です。

3. 再コンパイル時にも呼ばれる

MetaEditorでコードを修正してコンパイルすると、稼働中のEAに対してOnDeinit(REASON_RECOMPILE)→ OnInitの順で自動的に呼ばれます。開発中にファイルハンドルのリークなどが起きないよう、OnDeinitでの後片付けは確実に行いましょう。

4. グローバル変数のハンドルは必ずリセットする

ファイルハンドルやその他のリソースハンドルをグローバル変数で保持している場合、OnDeinitでクローズした後は必ずINVALID_HANDLEなどの無効値に戻してください。これにより、二重解放の問題を防止できます。

5. Comment(“”)でチャート表示をクリアする

Comment()関数でチャート上に情報を表示している場合、OnDeinitでComment("")を呼んでクリアしないと、EA削除後もテキストがチャートに残り続けます。

6. EventKillTimer()を忘れない

OnInitでEventSetTimer()を呼んでタイマーを設定している場合は、OnDeinitで必ずEventKillTimer()を呼んでタイマーを停止しましょう。

void OnDeinit(const int reason)
{
   // タイマー停止(OnInitでEventSetTimerを使用した場合は必須)
   EventKillTimer();
   
   // チャートコメントのクリア
   Comment("");
   
   // 自作オブジェクトの削除
   ObjectsDeleteAll(0, "MyPrefix_");
   
   // ファイルハンドルのクローズ
   if(fileHandle != INVALID_HANDLE)
   {
      FileClose(fileHandle);
      fileHandle = INVALID_HANDLE;
   }
   
   Print("すべてのクリーンアップが完了しました");
}

まとめ

OnDeinit関数は、プログラムの「品質」を決める重要な関数です。適切な後片付けを行うことで、メモリリークやファイル破損を防ぎ、チャートをきれいな状態に保てます。特に理由コードによる条件分岐を活用することで、パラメータ変更時と完全な終了時で異なるクリーンアップ処理を実装でき、より使いやすいEAやインジケーターを作成できます。