FXでは、週末や祝日をまたいだ直後に、

前日の終値と翌営業日の始値がズレる「窓(ギャップ)」ができることがあります。

既刊の記事では、この窓をチャート上に検出・可視化するインジケーターを作りました。

この記事では一歩進んで、

「窓は時間が経つと前日終値まで戻ってくることがある」という経験則(窓埋め)

に賭けて、実際に逆張りでエントリーするEAを作ります。

この記事で分かること
・窓の方向によってエントリー方向(買い・売り)を決める考え方
・前日終値を利確目標、本日始値の外側を損切りにする設計
・MarketInfoのMODE_STOPLEVELでブローカーの
・最小ストップ距離を確認する方法
・GlobalVariableで同じ窓への二重発注を防ぐ方法
  1. 設計思想: なぜ「窓埋め」を狙う逆張りにしたのか
    1. 窓ができたあとの値動きには2パターンある
    2. このEAが狙うのは「戻ってくる」ケースだけ
    3. 既刊2記事との役割の違い
  2. 窓の検出(既刊インジケーターと同じ日足固定方式)
    1. iOpen/iCloseで日足の窓を計算する
    2. 新しい日足バーが来た最初のtickだけ判定する
  3. エントリー方向・損切り・利確の設計
    1. 上昇ギャップは売り、下落ギャップは買い
    2. 利確は前日終値、損切りは本日始値+バッファ
    3. すでに窓がほとんど埋まっている場合は見送る
    4. ブローカーの最小ストップ距離(MODE_STOPLEVEL)を確認する
  4. 二重発注を防ぐGlobalVariable管理
    1. 日付と通貨ペアをキーにして1日1回だけ判定する
  5. 実際に動くコード全体
  6. MetaEditorでのコンパイル・バックテスト手順
  7. よくあるエラーと注意点
    1. 窓は必ず埋まるとは限らない
    2. 複数の通貨ペアに同時適用するとマジックナンバーが競合する
    3. バックテストのティックデータ精度に依存する
  8. よくある質問
    1. 既刊のgap-detection-indicator-mt4.mdと併用できますか?
    2. 損切りに引っかかったあと、同じ窓で再エントリーしますか?
    3. エントリー後に逆方向へ伸び続けたらどうなりますか?
    4. 窓が埋まらないまま次の週末を迎えたらどうなりますか?
  9. 【MQL4】窓(ギャップ)ができた直後に「窓埋め」を狙って逆張りエントリーするEAの作り方(MODE_STOPLEVELの安全確認つき):まとめ
    1. 関連記事

設計思想: なぜ「窓埋め」を狙う逆張りにしたのか

まず、窓ができたあとの値動きの性質を整理します。

窓ができたあとの値動きには2パターンある

補足
・パターン1: 価格が前日終値まで戻ってくる(窓埋め)
・パターン2: 窓の方向にそのまま伸び続ける(トレンド継続)
・どちらになるかは相場次第で、事前に確実な判定方法は無い

このEAが狙うのは「戻ってくる」ケースだけ

このEAは、パターン1(窓埋め)に賭けて逆張りします。

パターン2(トレンド継続)が起きた場合は損切りにかかる設計のため、

後述する損切り幅の設定が特に重要になります。

既刊2記事との役割の違い

記事 窓に対する立場 発注するか
既刊weekend-position-close-ea-mt4.md 窓リスクを避ける 決済のみ(新規発注なし)
既刊gap-detection-indicator-mt4.md 窓を記録・可視化する しない
本記事 窓が埋まることに賭ける する(逆張り)

窓の検出(既刊インジケーターと同じ日足固定方式)

既刊インジケーターと同じ考え方で、チャートの表示期間に関わらず窓を計算します。

iOpen/iCloseで日足の窓を計算する

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 gapPips = (todayOpen - prevClose) / point;

timeframeにPERIOD_D1を明示することで、

チャートがM1でもH4でも、常に日足ベースで窓の大きさを計算できます。

新しい日足バーが来た最初のtickだけ判定する

static datetime lastDailyBarTime = 0;

datetime currentDailyBarTime = iTime(NULL, PERIOD_D1, 0);
if(currentDailyBarTime == lastDailyBarTime)
   return;
lastDailyBarTime = currentDailyBarTime;

窓は1日に多くて1つしかできないため、

日足が切り替わった最初のtickでだけ判定を行います。

エントリー方向・損切り・利確の設計

窓の方向から、エントリー方向と損切り・利確のラインを決めます。

上昇ギャップは売り、下落ギャップは買い

補足
・始値が前日終値より上(上昇ギャップ)なら、下に戻ると見て売る
・始値が前日終値より下(下落ギャップ)なら、上に戻ると見て買う
・どちらも『前日終値の方向に戻る』ことを狙う逆張り

利確は前日終値、損切りは本日始値+バッファ

double sl = MathMax(todayOpen, Bid) + InpStopBufferPips * point; // 上昇ギャップ(売り)の場合
double tp = prevClose;

利確(tp)は窓が完全に埋まる水準(前日終値)に置き、

損切り(sl)は本日始値から

さらに少し外側(InpStopBufferPips分)に置きます。

補足
・本日始値ではなくMathMax(todayOpen, Bid)を基準にしている
・判定した時点でBidが始値よりさらに動いている場合
・損切りが現在値より近い(無効な)位置にならないようにするため

窓ができた直後は値動きが荒くなりやすいため、

始値ぴったりに損切りを置くと、

本来の窓埋めシナリオのままでも一時的に揺れて損切りされやすくなります。

すでに窓がほとんど埋まっている場合は見送る

double remainPips = (Bid - prevClose) / point; // 上昇ギャップの場合
if(remainPips < InpMinRemainPips)
   return; // 既に埋まりかけているため見送る

判定した時点で価格がすでに前日終値付近まで戻っていると、

利確までの距離が短すぎて、リスクに見合わない取引になります。

ブローカーの最小ストップ距離(MODE_STOPLEVEL)を確認する

double stopLevelPoints = MarketInfo(_Symbol, MODE_STOPLEVEL);
double minDistance = stopLevelPoints * _Point;

if((sl - Bid) < minDistance || (Bid - tp) < minDistance)
   return; // 最小ストップ距離を満たせないため発注しない

ブローカーによっては、現在値から損切り・利確までの距離に

最小値(MODE_STOPLEVEL)が決められています。

これを満たさないまま発注すると、サーバー側で拒否されるため、

事前に確認して満たせない場合は発注自体を取りやめます。

二重発注を防ぐGlobalVariable管理

同じ窓に対して、1日に何度も判定・発注しないようにします。

日付と通貨ペアをキーにして1日1回だけ判定する

string dateKey = "GFR_" + _Symbol + "_" +
                 TimeToString(currentDailyBarTime, TIME_DATE);

if(GlobalVariableCheck(dateKey))
   return; // 今日はもう判定済み
GlobalVariableSet(dateKey, 1);
注意
・GlobalVariableは端末を再起動しても残る(消えない)
・EAを一度外して再適用しても、同じ窓への再発注を防げる
・日付だけでなく_Symbolもキーに含めることで、複数通貨ペアで
・同じ日の窓の記録が衝突しないようにしている

実際に動くコード全体

ここまでの要素をまとめた、実際にMetaEditorでコンパイルできるコードです。

//+------------------------------------------------------------------+
//|                                      GapFillReversalEA.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 int    InpMinGapPips     = 50;       //エントリー対象にする最小ギャップ(pips)
input int    InpMinRemainPips  = 25;       //エントリー時点で残っている必要がある最小ギャップ(pips)
input double InpLots           = 0.10;     //発注ロット数
input int    InpStopBufferPips = 20;       //本日始値からさらに外側へ加える損切りバッファ(pips)
input int    InpSlippage       = 5;        //許容スリッページ
input int    InpMagic          = 20261115; //マジックナンバー

datetime lastDailyBarTime = 0;

//+------------------------------------------------------------------+
//| 5桁/3桁ブローカーの補正済みpoint値を返す                                |
//+------------------------------------------------------------------+
double PointAdjust()
  {
   double point = _Point;
   if(_Digits == 3 || _Digits == 5)
      point *= 10;
   return(point);
  }

//+------------------------------------------------------------------+
//| このマジックナンバー・この通貨ペアの保有ポジションがあるか                   |
//+------------------------------------------------------------------+
bool HasOpenPosition()
  {
   for(int i = 0; i < OrdersTotal(); i++)
     {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) &&
         OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagic)
         return(true);
     }
   return(false);
  }

//+------------------------------------------------------------------+
//| 窓埋めを狙った逆張りエントリーを試みる                                   |
//+------------------------------------------------------------------+
void TryEnterGapFillTrade()
  {
   double point = PointAdjust();

   double todayOpen = iOpen(NULL, PERIOD_D1, 0);
   double prevClose = iClose(NULL, PERIOD_D1, 1);
   double gapPips = (todayOpen - prevClose) / point;

   if(MathAbs(gapPips) < InpMinGapPips)
      return;

   string dateKey = "GFR_" + _Symbol + "_" +
                     TimeToString(iTime(NULL, PERIOD_D1, 0), TIME_DATE);
   if(GlobalVariableCheck(dateKey))
      return;
   GlobalVariableSet(dateKey, 1);

   double stopLevelPoints = MarketInfo(_Symbol, MODE_STOPLEVEL);
   double minDistance = stopLevelPoints * _Point;

   if(gapPips > 0)
     {
      double remainPips = (Bid - prevClose) / point;
      if(remainPips < InpMinRemainPips)
        {
         Print("窓が既にほとんど埋まっているため見送り: remainPips=", remainPips);
         return;
        }
      double sl = MathMax(todayOpen, Bid) + InpStopBufferPips * point;
      double tp = prevClose;
      if((sl - Bid) < minDistance || (Bid - tp) < minDistance)
        {
         Print("MODE_STOPLEVELを満たせないため見送り");
         return;
        }
      int ticket = OrderSend(_Symbol, OP_SELL, InpLots, Bid, InpSlippage, sl, tp,
                             "GapFillReversalEA", InpMagic, 0, clrRed);
      if(ticket < 0)
         Print("売り発注に失敗しました。エラーコード: ", GetLastError());
     }
   else if(gapPips < 0)
     {
      double remainPips = (prevClose - Ask) / point;
      if(remainPips < InpMinRemainPips)
        {
         Print("窓が既にほとんど埋まっているため見送り: remainPips=", remainPips);
         return;
        }
      double sl = MathMin(todayOpen, Ask) - InpStopBufferPips * point;
      double tp = prevClose;
      if((Ask - sl) < minDistance || (tp - Ask) < minDistance)
        {
         Print("MODE_STOPLEVELを満たせないため見送り");
         return;
        }
      int ticket = OrderSend(_Symbol, OP_BUY, InpLots, Ask, InpSlippage, sl, tp,
                             "GapFillReversalEA", InpMagic, 0, clrBlue);
      if(ticket < 0)
         Print("買い発注に失敗しました。エラーコード: ", GetLastError());
     }
  }

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

//+------------------------------------------------------------------+
//| Expert tick function                                              |
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime currentDailyBarTime = iTime(NULL, PERIOD_D1, 0);
   if(currentDailyBarTime == lastDailyBarTime)
      return;
   lastDailyBarTime = currentDailyBarTime;

   if(HasOpenPosition())
      return;

   TryEnterGapFillTrade();
  }
//+------------------------------------------------------------------+

日足が切り替わった最初のtickでだけ窓を判定し、

条件を満たしたときに1回だけ発注する構成です。

MetaEditorでのコンパイル・バックテスト手順

実際に動かすまでの手順です。

  1. MetaEditorで新規Expert Advisor「GapFillReversalEA」を作成し、上のコードを貼り付ける
  2. F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
  3. ストラテジーテスターで、窓ができやすい祝日・年末年始を含む期間でバックテストを実行する
  4. 「結果」タブで、月曜早朝など窓が開いたタイミングの直後に発注されているかを確認する

よくあるエラーと注意点

実装・運用でつまずきやすいポイントをまとめます。

窓は必ず埋まるとは限らない

注意
・重要な経済指標や要人発言の直後にできた窓は
・そのままトレンド継続することがある(窓埋めが起きないケース)
・このEAは窓埋めが起きる前提の逆張りのため
・損切り(InpStopBufferPips)の設定が特に重要になる

複数の通貨ペアに同時適用するとマジックナンバーが競合する

同じInpMagicのまま複数の通貨ペアチャートに適用すると、

GlobalVariableのキーには_Symbolが含まれているため、

窓の判定自体は競合しませんが、

OrderMagicNumberだけで自分の注文を数える他のEAと併用する場合は、

通貨ペアごとにInpMagicの値を変えることを推奨します。

バックテストのティックデータ精度に依存する

既刊backtest-live-gap-mt4.mdで触れたとおり、

ヒストリカルデータのティック粒度が粗いと、

窓ができた直後の値動き(本来は数秒〜数分単位で動く部分)が

バックテスト上で正確に再現されない場合があります。

よくある質問

窓埋め逆張りEAについて、よくある疑問に回答します。

既刊のgap-detection-indicator-mt4.mdと併用できますか?

できます。

インジケーターで窓の可視化だけを行い、

このEAで実際の発注判断を行う、という組み合わせが想定している使い方です。

損切りに引っかかったあと、同じ窓で再エントリーしますか?

しません。

GlobalVariableで1日1回しか判定しないため、

同じ日の同じ窓に対して再エントリーすることはありません。

エントリー後に逆方向へ伸び続けたらどうなりますか?

損切り(本日始値+バッファ)で決済されます。

このEAはトレンド継続のリスクを前提に、

発注時点で必ず損切りを設定する設計にしています。

窓が埋まらないまま次の週末を迎えたらどうなりますか?

損切り・利確のいずれかに達するまで、そのまま保持されます。

次の週に別の窓ができても、HasOpenPosition()の確認により、

保有中のポジションがある間は新規発注されません。

決済済みになってから、次の窓を新たに判定します。

【MQL4】窓(ギャップ)ができた直後に「窓埋め」を狙って逆張りエントリーするEAの作り方(MODE_STOPLEVELの安全確認つき):まとめ

この記事では、既刊の窓検出インジケーターを土台に、

窓が「埋まる」ことに賭けて実際に発注するEAを解説しました。

上昇ギャップは売り、下落ギャップは買いという逆張りの方向判定に加え、

前日終値を利確目標、本日始値の外側を損切りに設定し、

MarketInfoのMODE_STOPLEVELで

ブローカーの最小ストップ距離も確認する設計にしています。

窓は必ず埋まるとは限らないため、

損切りを必ず設定する発注設計と、

GlobalVariableによる1日1回の判定制限を組み合わせることで、

安全に検証できる形にまとめました。

くわしい自動化の開示方針は編集方針ページもご覧ください。

関連記事