EAのバックテストは、
スプレッドを一定の固定値として扱うことが多く、
実運用で実際にどれくらいスプレッドが変動しているかは、
ふだんは気づきにくいものです。
この記事では、1分ごとに実測スプレッドをCSVファイルへ記録しつつ、
その日の最小・最大・平均をチャート上に常時表示する、
監視インジケーターの作り方を解説します。
・バックテストと実運用でスプレッドの扱いが違う理由
・1分ごとの実測値をCSVに追記していく設計
・その日の最小・最大・平均をチャート上に表示する方法
・既刊の「現在値だけを表示する」インジケーターとの違い
設計思想: なぜスプレッドを記録する必要があるのか
まず、このインジケーターが解決する問題を整理します。
バックテストは固定スプレッドを前提にしがち
既刊のバックテストと実運用のズレを扱った記事でも触れたとおり、
MT4のストラテジーテスターは、
ヒストリーデータに含まれる固定スプレッド(または単一の設定値)で計算されます。
実際のスプレッドは時間帯によって大きく変わる
・スプレッド=買値(Ask)と売値(Bid)の差
・経済指標発表や市場の閑散時間帯は
・通常の数倍に広がることがある
既刊の「現在値表示」インジケーターとの違い
既刊のスプレッド表示インジケーターは、
今この瞬間のスプレッドだけを表示する設計でした。
本記事は「履歴として記録し、
後から振り返って集計する」という別の実装形態です。
スプレッドの取得と記録タイミングの判定
実測値をどのタイミングで取得するかを確認します。
MarketInfo(MODE_SPREAD)で取得する
int spreadPoints = (int)MarketInfo(_Symbol, MODE_SPREAD);
戻り値の単位はポイントです。
5桁ブローカーの場合、多くの通貨ペアで10ポイント=1pipsとなる点に注意してください。
新しい1分足が確定したタイミングだけ記録する
datetime currentMinute = iTime(_Symbol, PERIOD_M1, 0);
if(currentMinute != g_lastRecordedMinute)
{
g_lastRecordedMinute = currentMinute;
RecordSpread();
}
OnCalculateはティックごとに呼ばれるため、
1分足の開始時刻が前回と変わった瞬間だけを捉えて記録します。
CSVへの記録設計(追記)
既刊のファイル書き込み記事と同じ考え方で、
CSVに1行ずつ追記します。
FILE_READ|FILE_WRITEで追記モードにする
int handle = FileOpen("SpreadLog_" + _Symbol + ".csv",
FILE_READ | FILE_WRITE | FILE_CSV, ';');
if(handle != INVALID_HANDLE)
{
FileSeek(handle, 0, SEEK_END);
FileWrite(handle, TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS), spreadPoints);
FileClose(handle);
}
FILE_WRITEだけを指定すると開くたびに内容が消えるため、
既刊記事と同じく、必ずFILE_READを組み合わせてFileSeekで末尾に移動します。
ファイルの保存先はMQL4/Filesフォルダ配下に限定される
ファイル名を指定するだけで、
MT4のデータフォルダ内の「MQL4/Files」フォルダ以下に保存されます。
任意の場所には保存できません。
その日の集計(最小・最大・平均)を計算する
記録と同時に、チャート表示用の集計値も更新します。
日付が変わったら集計をリセットする
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int today = dt.day;
if(today != g_lastDay)
{
g_lastDay = today;
g_sumSpread = 0;
g_countSpread = 0;
g_minSpread = 999999;
g_maxSpread = 0;
}
最小・最大・平均を更新する
g_sumSpread += spreadPoints; g_countSpread++; if(spreadPoints < g_minSpread) g_minSpread = spreadPoints; if(spreadPoints > g_maxSpread) g_maxSpread = spreadPoints; double avgSpread = g_sumSpread / g_countSpread;
実際に動くコード全体
記録・集計・パネル表示までを組み込んだ、
実際にMetaEditorでコンパイルできるインジケーターです。
//+------------------------------------------------------------------+
//| SpreadLoggerIndicator.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
#property indicator_chart_window
#property indicator_buffers 0
#define LABEL_NAME "SpreadLoggerPanel"
datetime g_lastRecordedMinute = 0;
int g_lastDay = -1;
long g_sumSpread = 0;
int g_countSpread = 0;
int g_minSpread = 999999;
int g_maxSpread = 0;
int OnInit()
{
ObjectCreate(0, LABEL_NAME, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, LABEL_NAME, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, LABEL_NAME, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, LABEL_NAME, OBJPROP_YDISTANCE, 20);
EventSetTimer(1);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
EventKillTimer();
ObjectDelete(0, LABEL_NAME);
}
void OnTimer()
{
UpdatePanel();
}
void RecordSpread()
{
int spreadPoints = (int)MarketInfo(_Symbol, MODE_SPREAD);
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.day != g_lastDay)
{
g_lastDay = dt.day;
g_sumSpread = 0;
g_countSpread = 0;
g_minSpread = 999999;
g_maxSpread = 0;
}
g_sumSpread += spreadPoints;
g_countSpread++;
if(spreadPoints < g_minSpread) g_minSpread = spreadPoints;
if(spreadPoints > g_maxSpread) g_maxSpread = spreadPoints;
int handle = FileOpen("SpreadLog_" + _Symbol + ".csv",
FILE_READ | FILE_WRITE | FILE_CSV, ';');
if(handle != INVALID_HANDLE)
{
FileSeek(handle, 0, SEEK_END);
FileWrite(handle, TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS), spreadPoints);
FileClose(handle);
}
else
Print("ログファイルを開けませんでした。エラーコード=", GetLastError());
UpdatePanel();
}
void UpdatePanel()
{
if(g_countSpread == 0)
{
ObjectSetText(LABEL_NAME, "スプレッド記録: 本日分なし", 11, "Arial", clrGray);
return;
}
double avgSpread = (double)g_sumSpread / g_countSpread;
string text = StringFormat("本日のスプレッド(pt) 最小:%d 最大:%d 平均:%.1f 記録件数:%d",
g_minSpread, g_maxSpread, avgSpread, g_countSpread);
ObjectSetText(LABEL_NAME, text, 11, "Arial", clrWhite);
}
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[])
{
datetime currentMinute = iTime(_Symbol, PERIOD_M1, 0);
if(currentMinute != g_lastRecordedMinute)
{
g_lastRecordedMinute = currentMinute;
RecordSpread();
}
return(rates_total);
}
MetaEditorでのコンパイル・実行手順
実際にMT4で動かすまでの手順です。
- MetaEditorで新規インジケーター「SpreadLoggerIndicator」を作成し、上のコードを貼り付けてコンパイルする
- エラー・警告が0件であることを確認する
- チャートに適用し、左上のパネルに「記録件数」が1分ごとに増えていくことを確認する
- MT4のデータフォルダ(ファイル→データフォルダを開く)内の「MQL4/Files」フォルダで、SpreadLog_通貨ペア名.csvが作成されていることを確認する
- Excel等でCSVを開き、時間帯ごとのスプレッドの傾向を確認する
よくあるエラーと注意点
実装・運用でつまずきやすいポイントをまとめます。
複数の通貨ペアで同時に使うとファイルが分かれる
ファイル名に_Symbolを含めているため、
通貨ペアごとに別のCSVが作成されます。
全ペアを1つのファイルにまとめたい場合は、
ファイル名を固定文字列に変更してください。
ログファイルが肥大化し続ける
・1分ごとに記録するため1日あたり1440行増える
・長期間チャートに付けたままにすると容量が増え続ける
・定期的にファイルをリネームして退避することをおすすめする
ストラテジーテスターでは記録されない
ストラテジーテスターはスプレッドを固定値として扱うため、
バックテスト中にこのインジケーターを動かしても、
実測の変動は記録されません。
実際のデモ口座・リアル口座のチャートに適用してください。
よくある質問
スプレッド記録インジケーターについて、よくある疑問に回答します。
既刊のスプレッド表示インジケーターと両方使えますか?
使えます。既刊記事は現在値だけを大きく表示する用途、
本記事は履歴を記録して後から分析する用途のため、
目的が異なります。
1分ごとではなく秒単位で記録できますか?
g_lastRecordedMinuteの判定を、
iTime(PERIOD_M1,0)ではなく、
TimeCurrent()の秒単位の差分に変えれば可能ですが、
1日あたりの記録件数が大幅に増えるため、
ファイルサイズに注意してください。
記録したCSVをEAの判定に使えますか?
既刊のFileOpen/FileSeekの記事と同じ考え方で、
EA側からこのCSVを読み込み、
過去の平均スプレッドを発注条件に組み込むこともできます。
【MQL4】スプレッドを1分ごとに記録してCSVに集計する監視インジケーターの作り方:まとめ
バックテストのスプレッドは固定値のため、
実運用との差は実測しないと分かりません。
1分ごとにスプレッドをCSVへ記録し、
同時にその日の最小・最大・平均をチャート上に表示することで、
普段は気づきにくいスプレッドの変動を後から振り返って確認できます。
記録したCSVは、EAの発注条件に組み込むなど、
さらに発展させることもできます。
コメント