FXでは、土曜日の早朝から月曜日の早朝までは、
どのブローカーのサーバーでも取引が止まります。
このため金曜日の終値と月曜日の始値の間に、
ローソク足が1本も無いのに価格だけが飛んでいる
「窓(ギャップ)」ができることがあります。
祝日明けや、重要な経済指標の発表直後にも同じ現象が起きます。
この記事では、チャートの表示期間(M1でもH1でも)に関わらず、
常に日足ベースで窓の有無を検出し、矩形とテキストで可視化し、
窓が埋まったかどうかも追跡するインジケーターを作ります。
・なぜ日中のローソク足同士には「本物の窓」がほとんど無いのか
・チャートの表示期間に関わらず日足の窓だけを検出する方法
・OBJ_RECTANGLEとOBJ_TEXTを使った窓の可視化
・窓が埋まったかどうかを自動で追跡する仕組み
設計思想: なぜ「日足固定」で窓を判定するのか
まず、窓という現象そのものの性質を整理します。
日中のローソク足同士には「本物の窓」がほとんど無い
・FXは平日ほぼ24時間取引が続いている
・そのため同じ日の中のバーでは、前のバーの終値と次のバーの始値はほぼ同じ値になる
・M1やM5のチャートでOpen[0]-Close[1]を計算しても
・ブローカーのフィード上のわずかなズレしか出てこない
チャートの表示期間(現在のチャートのタイムフレーム)のまま窓を判定すると、
このわずかなズレをすべて「窓」として検出してしまい、
本当に意味のある窓が埋もれてしまいます。
窓は「土日・祝日・市場が止まった時間」をまたいだときに生まれる
本当に意味のある窓は、市場が実際に一定時間止まった後、次に値が付いたときに生まれます。
FXの場合、この「止まる時間」が確実に存在するのは日足の切り替わり(特に週末)なので、
日足を基準に判定するのが最も確実です。
「窓埋め」とは何か
窓埋めとは、窓ができたあとに価格が戻ってきて、
窓の前の価格水準(前日の終値)にもう一度触れることです。
窓埋めが起きるかどうかは相場次第で保証されたものではありませんが、
多くのトレーダーが意識する現象のため、
このインジケーターでは埋まったかどうかを自動で判定し、
表示を変えます。
新しい日足バーの検知とギャップの計算
チャートの表示期間に関わらず、日足を基準に判定する仕組みを作ります。
iOpen/iCloseでチャートの期間とは無関係に日足を取得する
double todayOpen = iOpen(NULL, PERIOD_D1, 0); double prevClose = iClose(NULL, PERIOD_D1, 1);
timeframeにPERIOD_D1を明示することで、現在のチャートがM1でもH4でも、
常に日足の始値・前日終値を取得できます。
新しい日足バーが確定した瞬間だけ処理する
static datetime lastDailyBarTime = 0; datetime currentDailyBarTime = iTime(NULL, PERIOD_D1, 0); if(currentDailyBarTime == lastDailyBarTime) return; lastDailyBarTime = currentDailyBarTime;
日足バーの開始時刻をstatic変数に記録し、前回と同じなら何もしないことで、
1日につき1回だけギャップを判定します。
ギャップをpips単位に変換する
double point = _Point; if(_Digits == 3 || _Digits == 5) point *= 10; double gapPoints = (todayOpen - prevClose) / point;
5桁ブローカーと4桁ブローカーの差を揃えるため、
既刊記事群と同じ_Digitsによる補正を入れます。
窓を矩形とテキストで描画する
検出した窓を、チャート上に残る形で表示します。
OBJ_RECTANGLEで2点を囲む
string rectName = "Gap_" + _Symbol + "_" +
TimeToString(currentDailyBarTime, TIME_DATE);
ObjectCreate(0, rectName, OBJ_RECTANGLE, 0,
iTime(NULL, PERIOD_D1, 1), prevClose,
currentDailyBarTime, todayOpen);
ObjectSetInteger(0, rectName, OBJPROP_BACK, true);
前日終値の時刻・価格と、
本日始値の時刻・価格を対角の2点として、
その間を矩形で塗ります。
・GlobalVariableは1つのターミナル内で共有される
・日付だけをキーにすると、別の通貨ペアの同じ日の窓と
・名前が衝突してしまうため、_Symbolも名前に含めている
上昇ギャップ・下落ギャップで色を変える
color gapColor = (gapPoints > 0) ? clrDeepSkyBlue : clrLightCoral; ObjectSetInteger(0, rectName, OBJPROP_COLOR, gapColor);
始値が前日終値より上なら上昇ギャップ、下なら下落ギャップとして、
一目で方向が分かるよう色を変えます。
OBJ_TEXTでギャップの大きさを添える
string textName = rectName + "_Text";
ObjectCreate(0, textName, OBJ_TEXT, 0, currentDailyBarTime, todayOpen);
ObjectSetString(0, textName, OBJPROP_TEXT,
"窓 " + DoubleToString(MathAbs(gapPoints), 1) + "pips");
ObjectSetInteger(0, textName, OBJPROP_COLOR, gapColor);
矩形だけでは大きさが分かりにくいため、pips数の文字ラベルを添えます。
窓埋めの判定と表示の更新
できた窓が、その後埋まったかどうかを追跡します。
窓埋めの判定条件
bool IsGapFilled(double gapPointsAtCreation, double prevCloseAtCreation)
{
if(gapPointsAtCreation > 0)
return(Bid <= prevCloseAtCreation); // 上昇ギャップは下に戻ってきたら埋まった
else
return(Bid >= prevCloseAtCreation); // 下落ギャップは上に戻ってきたら埋まった
}
上昇ギャップは価格が前日終値まで下がってくれば埋まり、
下落ギャップは価格が前日終値まで上がってくれば埋まったと判定します。
埋まったら色とラベルを変える
・窓埋めを検知したら、矩形の色をグレーに変える
・テキストラベルの末尾に「(埋め済み)」を追加する
・一度埋まった窓は、再度判定を行わない
・判定はGlobalVariableSetで矩形名をキーに記録する
古い窓を自動で削除する
if(ObjectsTotal(0, -1, OBJ_RECTANGLE) > InpMaxGaps * 2)
{
// 最も古い"Gap_"オブジェクトを1件削除する処理
}
窓を検出するたびにオブジェクトが増え続けると、チャートが重くなるため、
保持する窓の数に上限(InpMaxGaps)を設けます。
実際に動くコード全体
ここまでの要素をまとめた、実際にMetaEditorでコンパイルできるコードです。
//+------------------------------------------------------------------+
//| GapDetectionIndicator.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
input int InpMinGapPoints = 50; //窓として扱う最小ポイント数
input int InpMaxGaps = 20; //保持する窓の最大件数
datetime lastDailyBarTime = 0;
//+------------------------------------------------------------------+
//| 窓が埋まったかどうかを判定する |
//+------------------------------------------------------------------+
bool IsGapFilled(double gapPointsAtCreation, double prevCloseAtCreation)
{
if(gapPointsAtCreation > 0)
return(Bid <= prevCloseAtCreation);
else
return(Bid >= prevCloseAtCreation);
}
//+------------------------------------------------------------------+
//| 保持件数を超えた古い窓オブジェクトを1件削除する |
//+------------------------------------------------------------------+
void CleanupOldGaps()
{
int total = ObjectsTotal(0, -1, OBJ_RECTANGLE);
if(total <= InpMaxGaps)
return;
for(int i = 0; i < ObjectsTotal(0, -1, -1); i++)
{
string name = ObjectName(0, i, -1, -1);
if(StringFind(name, "Gap_") == 0 && StringFind(name, "_Text") < 0)
{
ObjectDelete(0, name);
ObjectDelete(0, name + "_Text");
break;
}
}
}
//+------------------------------------------------------------------+
//| 新しい日足バーの窓を検出して描画する |
//+------------------------------------------------------------------+
void CheckNewGap()
{
datetime currentDailyBarTime = iTime(NULL, PERIOD_D1, 0);
if(currentDailyBarTime == lastDailyBarTime)
return;
lastDailyBarTime = currentDailyBarTime;
double todayOpen = iOpen(NULL, PERIOD_D1, 0);
double prevClose = iClose(NULL, PERIOD_D1, 1);
double point = _Point;
if(_Digits == 3 || _Digits == 5)
point *= 10;
double gapPoints = (todayOpen - prevClose) / point;
if(MathAbs(gapPoints) < InpMinGapPoints)
return;
string rectName = "Gap_" + _Symbol + "_" + TimeToString(currentDailyBarTime, TIME_DATE);
string textName = rectName + "_Text";
if(ObjectFind(0, rectName) >= 0)
return;
color gapColor = (gapPoints > 0) ? clrDeepSkyBlue : clrLightCoral;
ObjectCreate(0, rectName, OBJ_RECTANGLE, 0,
iTime(NULL, PERIOD_D1, 1), prevClose,
currentDailyBarTime, todayOpen);
ObjectSetInteger(0, rectName, OBJPROP_COLOR, gapColor);
ObjectSetInteger(0, rectName, OBJPROP_BACK, true);
ObjectSetInteger(0, rectName, OBJPROP_FILL, true);
ObjectCreate(0, textName, OBJ_TEXT, 0, currentDailyBarTime, todayOpen);
ObjectSetString(0, textName, OBJPROP_TEXT,
"窓 " + DoubleToString(MathAbs(gapPoints), 1) + "pips");
ObjectSetInteger(0, textName, OBJPROP_COLOR, gapColor);
ObjectSetInteger(0, textName, OBJPROP_FONTSIZE, 8);
GlobalVariableSet("GapFilled_" + rectName, 0);
GlobalVariableSet("GapDir_" + rectName, gapPoints);
GlobalVariableSet("GapPrice_" + rectName, prevClose);
CleanupOldGaps();
}
//+------------------------------------------------------------------+
//| 既存の窓が埋まったかどうかを毎tick確認する |
//+------------------------------------------------------------------+
void CheckGapFilled()
{
for(int i = 0; i < ObjectsTotal(0, -1, OBJ_RECTANGLE); i++)
{
string name = ObjectName(0, i, -1, OBJ_RECTANGLE);
if(StringFind(name, "Gap_") != 0)
continue;
if(GlobalVariableCheck("GapFilled_" + name) == false)
continue;
if(GlobalVariableGet("GapFilled_" + name) > 0)
continue;
double gapPointsAtCreation = GlobalVariableGet("GapDir_" + name);
double prevCloseAtCreation = GlobalVariableGet("GapPrice_" + name);
if(IsGapFilled(gapPointsAtCreation, prevCloseAtCreation))
{
ObjectSetInteger(0, name, OBJPROP_COLOR, clrGray);
ObjectSetString(0, name + "_Text", OBJPROP_TEXT,
ObjectGetString(0, name + "_Text", OBJPROP_TEXT) + "(埋め済み)");
GlobalVariableSet("GapFilled_" + name, 1);
}
}
}
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
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[])
{
CheckNewGap();
CheckGapFilled();
return(rates_total);
}
//+------------------------------------------------------------------+
日足が切り替わった瞬間に窓を検出し、その後は毎tickで窓埋めの判定だけを行う構成です。
MetaEditorでのコンパイル・動作確認手順
実際に動かすまでの手順です。
- MetaEditorで新規インジケーター「GapDetectionIndicator」を作成し、上のコードを貼り付けてコンパイルする
- 「0 error(s), 0 warning(s)」になることを確認する
- 任意の通貨ペアの日足・または任意の時間足チャートに適用する
- 過去の月曜日の始値近辺に、矩形とpipsラベルが表示されることを確認する
- その後の値動きで前日終値まで戻ったとき、矩形の色が灰色に変わり「(埋め済み)」の表示が追加されることを確認する
よくあるエラーと注意点
実装・運用でつまずきやすいポイントをまとめます。
M1チャートに入れても窓が全く出ないと誤解する
・このインジケーターはチャートの表示期間に関わらずPERIOD_D1を見ている
・そのため1日に多くて1つしか窓が表示されない(仕様どおり)
・M1チャートのバーごとに窓が出る、という動作ではない
InpMinGapPointsを小さくしすぎるとノイズが増える
スプレッドの広いペアや、ブローカーの週末配信の仕様によっては、
非常に小さい値でも「窓」として記録されてしまい、
本当に注目すべき大きな窓が埋もれることがあります。
まずはデフォルトの50ポイント前後から調整してください。
既刊weekend-position-close-ea-mt4.mdとは目的が異なる
既刊weekend-position-close-ea-mt4.mdは、
窓が開く前にポジションを決済して窓リスクそのものを避けるEAでしたが、
本記事のインジケーターは窓を避けるのではなく、できた窓を記録・可視化することが目的です。
よくある質問
価格ギャップ検出インジケーターについて、よくある疑問に回答します。
このインジケーターは発注まで行いますか?
行いません。
チャートに矩形とテキストを表示するだけで、注文は一切出しません。
窓は必ず埋まりますか?
保証されたものではありません。
窓埋めは多くのトレーダーが意識する経験則的な現象であり、
埋まらずに一方向へ伸び続けることもあります。
このインジケーターは「埋まったかどうか」を記録するだけで、
将来埋まることを予測するものではありません。
1分足チャートでも日足の窓を検出できますか?
できます。
iOpen/iCloseの第2引数にPERIOD_D1を明示しているため、
チャートの表示期間とは無関係に日足の窓を検出します。
【MQL4】日足の始値と前日終値の差から「価格ギャップ(窓)」を検出・表示するインジケーターの作り方(窓埋め判定つき):まとめ
この記事では、FXの土日・祝日をまたぐときに発生する「窓(ギャップ)」
を、チャートの表示期間に関わらず日足ベースで検出し、
矩形とテキストで可視化するインジケーターを解説しました。
日中のローソク足同士にはほとんど「本物の窓」が無いという前提を踏まえ、
iOpen/iCloseの第2引数をPERIOD_D1に固定することで、
M1チャートでもH4チャートでも同じ判定ができる設計にしています。
さらに、窓ができたあとに価格が前日終値まで戻ってくる「窓埋め」
も自動で追跡し、矩形の色とラベルの変化で一目で分かるようにしました。
くわしい自動化の開示方針は編集方針ページもご覧ください。
コメント