`OnTradeTransaction()`というイベントハンドラを使い、
決済が起きた「その瞬間」を検知してCSVに書き込みました。
ところがMQL4には、この`OnTradeTransaction()`に
相当する仕組みが存在しません。
この記事では、MQL4で同じ「決済のたびにCSVへ1行追記する」
機能を実現するために、`OrdersHistoryTotal()`を
毎ティック確認する「ポーリング方式」で代用する設計を解説します。
なぜMQL4にも自分だけの決済ログが必要なのか
MT4のバックテストレポートは、テスト終了後にまとめて生成されるものであり、
実運用中の挙動をリアルタイムには確認できません。
また`Print()`の出力はMT4の「エキスパート」タブに表示されますが、
保持できる行数には限りがあり、古いログは流れて消えてしまいます。
決済のたびにCSVへ書き出しておけば、
Excelなどで後から自由に集計・グラフ化できます。
この記事で作るもの: ポジションが決済されるたびに、
決済時刻・銘柄・方向・ロット数・決済損益の5項目を1行として、
CSVファイルの末尾に自動で追記するEAです。
FileOpenでファイルを開く:引数とフラグの意味
公式リファレンス(FileOpen)によると、MQL4のシグネチャは次の通りです。
int FileOpen( string file_name, //ファイル名 int open_flags, //開き方を指定するフラグの組み合わせ short delimiter=';', //CSVモードでの区切り文字(省略時はセミコロン) uint codepage=CP_ACP //文字コード(省略時は既定のANSI) );
戻り値は、成功すればファイルハンドル(`int`)、
失敗すれば`INVALID_HANDLE`です。
この記事で使うフラグの組み合わせはMQL5版と同じです。
FILE_WRITE | FILE_CSV | FILE_ANSI
追記のためのFILE_READ|FILE_WRITE
`FILE_WRITE`だけを指定して開くと、
開くたびにファイルの中身が空にリセットされます。
過去のログに追記したい場合は、
必ず`FILE_READ|FILE_WRITE`を組み合わせ、
`FileSeek(handle, 0, SEEK_END)`で
末尾に移動してから書き込んでください。
FileWriteで1行書き込む仕組み
uint FileWrite(int file_handle, ...);
第1引数はハンドル、それ以降は書き込みたい値を
カンマ区切りで好きなだけ並べられます(最大63個)。
`FILE_CSV`モードでは、渡した値の間に区切り文字が自動挿入され、
呼び出しの最後に自動で改行(`\r\n`)が付きます。
MQL5版と全く同じ仕組みなので、一度覚えれば両方に応用できます。
決済を検知する方法がMQL5と全く違う:OrdersHistoryTotalのポーリング設計
ここがこの記事の一番の要点です。
MQL5版は`OnTradeTransaction()`が決済の瞬間を教えてくれましたが、
MQL4のEAには、そもそも取引イベントを直接通知してくれる仕組みがありません。
MQL4にはOnTradeTransactionが無い
そこでこの記事では、`OnTick()`が呼ばれるたびに
「決済済み注文の総数」を表す`OrdersHistoryTotal()`を確認し、
前回チェック時より件数が増えていたら、
増えた分だけ新しく決済された注文とみなして処理する設計にします。
用語: OrdersHistoryTotal()は、
決済済み・キャンセル済みの注文数を返す関数です。
OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)
と組み合わせて、過去の注文情報を1件ずつ参照します。
二重記録を防ぐ「処理済み件数」の管理
「前回チェック時の件数」を`static`変数(またはグローバル変数)で覚えておき、
今回の件数との差分だけを処理することで、
同じ決済を2回ログに書いてしまう二重記録を防ぎます。
注意: この方式は、EAがチャートから外れている間に決済された注文を、
再度EAを読み込んだ時点で処理済みの範囲に含めてしまい、
記録し損ねる可能性があります。
ナンピン抑止EAの記事で解説した限界と同じく、
「EAが動いている間しか機能しない」という前提を理解した上で使ってください。
OrderCloseTime/OrderProfitで決済情報を取得する
増えた分の注文を1件ずつ選択したら、
`OrderMagicNumber()`によるフィルタリングと同じ考え方で、
自分のEA以外の注文(裁量トレードや他のEA)を除外できるようにしておきます。
実際に動くコード全体
決済済み注文が増えるたびに、その分だけCSVへ追記する、
実際にMetaEditorでコンパイルできるコードです。
//+------------------------------------------------------------------+
//| DealLogWriterMt4.mq4 |
//| 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"
#property strict
input string LogFileName = "deal_log_mt4.csv";//保存するCSVファイル名
input int MagicNumberFilter = 0;//0=フィルタなし(全ポジション対象)
int loggedHistoryCount = 0;//前回チェック時点までに記録済みの履歴件数
//+------------------------------------------------------------------+
//| ログファイルの先頭にヘッダー行を書き込む(初回のみ) |
//+------------------------------------------------------------------+
void WriteHeaderIfNeeded()
{
if(FileIsExist(LogFileName))
return;//既にファイルがあればヘッダーは書かない
int handle = FileOpen(LogFileName, FILE_WRITE|FILE_CSV|FILE_ANSI, ',');
if(handle == INVALID_HANDLE)
{
Print("ログファイルのヘッダー作成に失敗しました。エラーコード=", GetLastError());
return;
}
FileWrite(handle, "決済時刻", "銘柄", "方向", "ロット", "決済損益");
FileClose(handle);
}
//+------------------------------------------------------------------+
//| 決済済みの1件を末尾に追記する |
//+------------------------------------------------------------------+
void AppendDealLog(datetime closeTime, string symbol, string direction, double lots, double profit)
{
int handle = FileOpen(LogFileName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_ANSI, ',');
if(handle == INVALID_HANDLE)
{
Print("ログファイルを開けませんでした。エラーコード=", GetLastError());
return;
}
FileSeek(handle, 0, SEEK_END);//末尾に移動してから追記する
FileWrite(handle, TimeToString(closeTime, TIME_DATE|TIME_SECONDS), symbol, direction,
DoubleToString(lots, 2), DoubleToString(profit, 2));
FileClose(handle);
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
WriteHeaderIfNeeded();
loggedHistoryCount = OrdersHistoryTotal();//起動時点までの履歴は記録済み扱いにする
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
int currentHistoryCount = OrdersHistoryTotal();
if(currentHistoryCount <= loggedHistoryCount)
return;//新しく増えた決済履歴がなければ何もしない
for(int i = loggedHistoryCount; i < currentHistoryCount; i++)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
continue;
if(MagicNumberFilter != 0 && OrderMagicNumber() != MagicNumberFilter)
continue;
if(OrderType() != OP_BUY && OrderType() != OP_SELL)
continue;//入出金等(OP_BALANCE/OP_CREDIT)は対象外にする
string direction = (OrderType() == OP_BUY) ? "買い決済" : "売り決済";
AppendDealLog(OrderCloseTime(), OrderSymbol(), direction, OrderLots(), OrderProfit());
}
loggedHistoryCount = currentHistoryCount;
}
//+------------------------------------------------------------------+
MetaEditorで実際に動かす手順
- MT4の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「Expert Advisor(テンプレートを使用)」を選び、名前を「DealLogWriterMt4」にして作成する
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- デモ口座のチャートへドラッグ&ドロップし、成行注文を1回出して決済し、「ファイル」→「データフォルダを開く」→「MQL4/Files」に
deal_log_mt4.csvが作成されているか確認する
MQL5版との違い:イベント駆動 vs ポーリング
同じ「決済ログをCSVに残す」という目的でも、実現方式が根本的に異なります。
| 項目 | MQL4(この記事) | MQL5 |
|---|---|---|
| 決済の検知方式 | OnTick()内でOrdersHistoryTotal()をポーリング | OnTradeTransaction()がイベントとして通知 |
| 検知のタイミング | 次のティックが来るまで最大数秒遅れる可能性 | ほぼ即座に呼ばれる |
| 二重記録対策 | 処理済み件数を自前でstatic変数管理 | DEAL_ENTRY_OUTの判定で1件ずつ確実に捕捉 |
| FileOpen/FileWriteの使い方 | ほぼ同じ(delimiter既定値のみ異なる) | ほぼ同じ(delimiter既定値のみ異なる) |
MQL4でOrdersHistoryTotalをポーリングする設計は、
ティックが来ない(相場が動かない)時間帯には決済の検知も遅れるという弱点があります。
数秒〜数十秒の遅延が許容できないシビアな用途では、
この方式の限界として理解しておいてください。
コピペしてコンパイルが通らない・ログが出力されないときに確認すること
- ログが1件も記録されない: `OnInit()`で`loggedHistoryCount`を`OrdersHistoryTotal()`の現在値で初期化しているため、EA起動より前に決済した注文は記録されない。これは仕様であり、起動後に新しく決済した注文から記録される
- 同じ決済が2回記録される: `loggedHistoryCount`の更新(`loggedHistoryCount = currentHistoryCount;`)を`for`ループの外に書いているか確認する。ループ内で毎回更新すると、判定がずれて重複記録の原因になる
- ファイルは作られるが中身が空になる: `FILE_READ`を付けずに`FILE_WRITE`だけで開いていないか確認する
- Excelで開くと日本語が文字化けする: `FILE_ANSI`を付けているか確認する
- 他のEAや裁量トレードの決済まで記録されてしまう: `MagicNumberFilter`が`0`(フィルタなし)のままになっていないか確認する
よくある質問
MQL4のファイル操作でよく検索されている疑問を、3つにまとめて回答します。
OrdersHistoryTotalの並び順は決済した順番と保証されていますか?
MQL4の公式リファレンスには、
`SELECT_BY_POS`+`MODE_HISTORY`の並び順が
決済時刻順であると明記した記載は見当たりません。
多くのEAで経験的にこの並びが使われていますが、
この記事のコードでも`OrderCloseTime()`をログに含めているため、
万が一並び順がずれても、CSV側で時刻順に並べ替えて確認できます。
保存したCSVファイルはどこにありますか?
MT4端末の「ファイル」→「データフォルダを開く」から、
MQL4/Filesフォルダの中に保存されます。
インジケーターからでもファイルに書き込めますか?
できますが、複数チャートに重複して読み込まれると
同じファイルへ同時に書き込もうとして競合する可能性があります。
口座全体の決済を管理しやすいEA側での実装を推奨します。
【MQL4】決済ログを自動でCSVに残す。FileOpen/FileWriteの使い方(MQL5版のOnTradeTransactionとの違い):まとめ
この記事では、MQL4のFileOpen・FileWriteを使って、
決済が起きるたびにCSVファイルへ1行ずつログを追記するEAを解説しました。
MQL5版との最大の違いは、決済を検知する方法です。
MQL5はOnTradeTransaction()というイベントで即座に検知できますが、
MQL4にはその仕組みがないため、
OrdersHistoryTotal()を毎ティック確認する
ポーリング方式で代用する必要があります。
この違いを理解しておくと、
MQL5版の記事で解説したコードとの対応関係も見えやすくなります。
コメント