「決済ログCSVに、新しい行が追記されているか定期的に確認したい」。

そう思って毎回ファイルを先頭から全部読み直すコードを書くと、

ログが数百行、数千行と増えるにつれて、

同じ内容を何度も読み直す無駄な処理になっていきます。

決済のたびにCSVへ1行追記するログ機能の記事で作った`deal_log.csv`を例に、

この記事では`FileSeek()`と`FileTell()`を使って、

「前回読み終えた位置」から続きの行だけを読み込む設計を解説します。

なぜ「先頭から毎回読み直す」と処理が重くなるのか

`FileOpen()`でファイルを開いた直後は、

ファイルポインタ(読み書きする位置)が常に先頭(バイト0)にあります。

ここから`FileReadString()`を繰り返してファイルの終わりまで読み切ると、

1行だけ追記された場合でも、

既に読んだはずの過去の行をすべて読み直すことになります。

ログが増えるほど無駄な処理時間が増え、

MT5の1ティックあたりの処理負荷にも影響します。

`FileSeek()`でファイルポインタを狙った位置まで移動できれば、

前回読み終えた位置から続きだけを読み込めるようになります。

FileSeek/FileTellの書式

公式リファレンス(FileSeek)によると、シグネチャは次の通りです。

bool FileSeek(
   int                 file_handle,  //FileOpen()が返したハンドル
   long                offset,       //移動するバイト数(負の値も可)
   ENUM_FILE_POSITION  origin        //どこを基準に移動するか
   );

FileSeekでファイルポインタを移動する

第3引数の`origin`には、

移動の基準点として`SEEK_SET`(ファイルの先頭)、

`SEEK_CUR`(現在位置)、

`SEEK_END`(ファイルの末尾)のいずれかを指定します。

「前回読んだ続きから読む」用途では、保存しておいたバイト位置を`offset`に渡し、

`SEEK_SET`を基準にすればよいことになります。

FileSeek(handle, lastPos, SEEK_SET);

戻り値は`bool`で、移動に成功すれば`true`、失敗すれば`false`が返ります。

FileTellで現在位置を取得する

公式リファレンス(FileTell)によると、シグネチャは次の通りです。

ulong FileTell(int file_handle);

戻り値は「ファイルの先頭からのバイト単位での距離」です。

読み込みを終えたタイミングで`FileTell()`を呼べば、

「今どこまで読んだか」をそのまま次回のための保存値として使えます。

MQL4にもFileSeek/FileTellは存在する(思い込みに注意)

補足: 「MQL4にはランダムアクセス関数がない」と思い込みがちですが、

実際にはMQL4にも同名の`FileSeek()`・`FileTell()`が存在し、

引数の構成もほぼ同じです。

この記事のテーマで両バージョンの違いを探すなら、

関数の有無ではなく、

次のH2で扱う「開くときのモード」の方に注意が必要です。

「前回の続きから」だけを読み込む設計

前回読み終えたバイト位置をどこかに保存しておかなければ、

次回起動したときに「前回の続き」が分かりません。

GlobalVariableで読み込み位置を保存する

MT5を再起動してもEAをまたいで値を覚えておきたいので、GlobalVariableSet/GlobalVariableGetの記事で解説したグローバル変数を使います。

double savedPos = GlobalVariableGet("DealLogTailPos");
GlobalVariableSet("DealLogTailPos", (double)FileTell(handle));

`GlobalVariableGet()`は変数が存在しない場合`0`を返すため、

初回実行時は自然に「先頭(バイト0)から読む」という動作になります。

FILE_CSVで開くと1行ずつ読めない落とし穴

注意: deal_log.csvを書き込むときは`FILE_CSV`モードで開きましたが、

読み込み用に開くときは`FILE_TXT`モードにする必要があります。

公式リファレンス(FileReadString)によると、

`FILE_CSV`モードの`FileReadString()`は、

区切り文字までの1フィールド単位、

`FILE_TXT`モードは改行までの1行単位で文字列を返します。

`FILE_CSV`のまま読み込むと、1回の`FileReadString()`で

「時刻」「通貨ペア」のような1列分しか取得できず、

保存したバイト位置と行の境界がズレていきます。

int handle = FileOpen(LogFileName, FILE_READ|FILE_TXT|FILE_ANSI);

書き込みは`FILE_CSV`、読み込みは`FILE_TXT`と、

同じファイルでも目的に応じてモードを使い分けてください。

ログファイルが作り直されていた場合の対処

保存していたバイト位置が、実際のファイルサイズより大きい場合は、

ログファイルが何らかの理由で作り直された(短くなった)可能性があります。

そのまま`FileSeek()`するとファイルの末尾を超えた位置を指定してしまうため、

`FileSize()`と比較し、超えていれば位置を`0`にリセットします。

if(lastPos > FileSize(handle))
   lastPos = 0;//ファイルが作り直された可能性があるため先頭から読み直す

実際に動くコード全体(ログを定期的に巡回するEA)

`deal_log.csv`を5秒おきにチェックし、

前回チェック以降に追記された行だけを`Print()`する、

実際にMetaEditorでコンパイルできるEAです。

//+------------------------------------------------------------------+
//|                                       DealLogTailWatcher.mq5      |
//|                     Copyright 2026, FX-EA System Project Creator |
//|                        https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link      "https://creator.fx-ea-system-project.com/"
#property version   "1.00"

input string LogFileName     = "deal_log.csv";//監視するログファイル名
input int    TailIntervalSec = 5;//巡回する間隔(秒)

//+------------------------------------------------------------------+
//| Expert initialization function                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
   EventSetTimer(TailIntervalSec);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
  }

//+------------------------------------------------------------------+
//| Timer function                                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
   TailLogFile();
  }

//+------------------------------------------------------------------+
//| 前回読み終えた位置から続きだけを読み込む                           |
//+------------------------------------------------------------------+
void TailLogFile()
  {
   if(!FileIsExist(LogFileName))
      return;

   int handle = FileOpen(LogFileName, FILE_READ|FILE_TXT|FILE_ANSI);
   if(handle == INVALID_HANDLE)
     {
      Print("ログファイルを開けませんでした。エラーコード=", GetLastError());
      return;
     }

   long lastPos = (long)GlobalVariableGet("DealLogTailPos");

   if(lastPos > (long)FileSize(handle))
      lastPos = 0;//ファイルが作り直された可能性があるため先頭から読み直す

   if(lastPos > 0)
     {
      if(!FileSeek(handle, lastPos, SEEK_SET))
        {
         Print("FileSeekに失敗しました。エラーコード=", GetLastError());
         FileClose(handle);
         return;
        }
     }

   int newLineCount = 0;
   while(!FileIsEnding(handle))
     {
      string line = FileReadString(handle);
      if(StringLen(line) == 0)
         continue;

      newLineCount++;
      Print("新しいログ行: ", line);
     }

   if(newLineCount > 0)
      GlobalVariableSet("DealLogTailPos", (double)FileTell(handle));

   FileClose(handle);
  }
//+------------------------------------------------------------------+

MetaEditorで実際に動かす手順

  1. MT5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
  2. 「ファイル」→「新規作成」→「Expert Advisor(テンプレート)」を選び、名前を「DealLogTailWatcher」にして作成する
  3. テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
  4. F7キーでコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
  5. 先に決済ログを書き込むEAで`deal_log.csv`を作成しておき、このEAをデモ口座のチャートへドラッグ&ドロップして「エキスパート」タブに新しい行が表示されるか確認する

よくあるエラー・意図と違う動きになるとき

  • 毎回すべての行を読み直してしまう: `GlobalVariableSet()`を呼ぶ条件が`newLineCount > 0`になっているか確認する。ログに変化がないときは位置を更新しないため、この条件を消すと不具合につながる
  • コンパイルエラーになる: `FileSeek()`の第3引数に`SEEK_SET`ではなく文字列や数値を直接渡していないか確認する。`ENUM_FILE_POSITION`型の定数を指定する必要がある
  • 一部の行しか表示されない: 書き込み側のEAで`FileFlush()`(または`FileClose()`)が呼ばれる前にこのEAが読み込むと、OSのバッファに残ったままの行を読み逃すことがある。書き込み側が確実にファイルへ反映してから読み込む設計にする
  • `INVALID_HANDLE`でファイルを開けない: 監視対象のログファイルがMT5の「MQL5/Files」フォルダに存在するか、`FileIsExist()`の結果と合わせて確認する

よくある質問

FileSeek/FileTellでよく検索されている疑問を、3つにまとめて回答します。

FileSeekはファイルの末尾より後ろも指定できますか?

書き込みモードで開いている場合は、

ファイル末尾より後ろへ移動して書き込むと、

間の部分がすき間として埋められる仕様の言語もありますが、

この記事のような読み込み専用の用途では、

`FileSize()`で得られる範囲内に収めるようにしてください。

グローバル変数を使わずに位置を保存する方法はありますか?

別のファイルに位置(バイト数)だけを保存する方法もありますが、

1つの整数値を保存するだけならグローバル変数の方が手軽です。

複数のEAやインジケーターから同じログを監視する場合は、

変数名が重複しないよう、

`GlobalVariableSet()`のキー名を用途ごとに変えてください。

巡回間隔を短くしすぎるとどうなりますか?

`TailIntervalSec`を極端に短くすると、

ファイルの開閉処理自体の負荷が増えます。

ログの用途がリアルタイム性を強く求めないのであれば、

数秒〜数十秒程度の間隔で十分実用的です。

【MQL5入門】FileSeek/FileTellで決済ログCSVの「前回の続きから」だけを読み返す(MQL4との違い):まとめ

この記事では、`FileSeek()`と`FileTell()`を使って、

ログファイルを毎回先頭から読み直すのではなく、「前回読み終えた位置」

から続きだけを読み込む設計を解説しました。

ポイントは、保存したバイト位置と行の境界をズレさせないために、

読み込み時は`FILE_CSV`ではなく`FILE_TXT`モードで開くことと、

ログファイルが作り直されていた場合に備えて、

`FileSize()`と比較して位置をリセットする工夫です。

この仕組みは、決済ログを書き込む記事で作ったCSVに限らず、

外部ツールが更新するテキストファイルをMT5側から監視したい場面全般に応用できます。

関連記事