「決済ログ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`モードにする必要があります。
`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で実際に動かす手順
- MT5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「Expert Advisor(テンプレート)」を選び、名前を「DealLogTailWatcher」にして作成する
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キーでコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- 先に決済ログを書き込む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側から監視したい場面全般に応用できます。
コメント