English Deutsch
preview
初心者からエキスパートへ:MQL5におけるマルチタイムフレーム構造確認による流動性戦略の強化

初心者からエキスパートへ:MQL5におけるマルチタイムフレーム構造確認による流動性戦略の強化

MetaTrader 5 |
19 1
Clemence Benjamin
Clemence Benjamin

M1~M15では技術的に完璧に見えるセットアップであっても、H1、H4、D1などの上位足(HTF)の流動性レベルに到達した途端、ほぼ即座に失敗してしまう。このような状況は繰り返し発生します。複数のチャートを手作業で監視し、上位足の状況を確認する作業は時間がかかるうえ、判断ミスや見落としを招きやすく、結果として有望なエントリーチャンスを逃してしまいます。そこで本記事では、明確な受け入れ基準を持つ実装志向の解決策を提案します。具体的には、指定した上位足の流動性ゾーンを自動的に検出し、そのゾーンを現在のチャート上に表示するとともに、価格がゾーンへ戻り、さらに定義済みの下位足の反転条件を満たした場合にのみ、一度だけ発生するエントリーアラートを生成します。また、ZoneTimeframe、LookbackBars、RatioMultiplier、ExtendBars、許可する反転パターン、およびアラートのスロットリング(通知頻度の制御)といった入力パラメータや制約条件も明確に定義します。

この課題を解決するため、以下のようなレイヤードアーキテクチャを採用します。

  1. ゾーン検出:設定可能な倍率を用いて、選択した上位足のベース→インパルスパターンをスキャンし、供給ゾーンおよび需要ゾーンを定義します。
  2. 表示レイヤー:検出したゾーンを矩形オブジェクトとして、時間足を変更しても表示が維持されるようにように現在のチャートへ描画します。
  3. リアクション監視:下位足で確定した各バーについて、価格がゾーンと重なっているかを判定するとともに、定義済みの反転パターン(包み足、ピンバー、インサイドバーブレイクアウト)のいずれかが出現したかを評価します。

実際のインジケータは、OnCalculate関数内で2段階の処理を実行します。第1パスでは、過去チャートの分析用に履歴シグナルを描画し、第2パスでは、ゾーンごとのトリガーフラグとアラートのスロットリング機構を利用して、各ゾーンにつき1回だけリアルタイムアラートを生成します。その結果、煩雑で判断がぶれやすいマルチタイムフレームの確認作業を、自動化された、再現性と検証性を備えたワークフローへと置き換えることができます。


内容


はじめに

マルチタイムフレーム分析とは、単に「上位足を確認して方向性を判断する」ことではありません。本当の意味で重要なのは、市場全体の意図が、自分のトレードする小さな価格変動をどのように支配しているかを理解することです。マクロコンテキストでは、大口参加者がどこにポジションを構築しているのか、主要な流動性ゾーンがどこに存在するのか、市場全体を支配するトレンドやセンチメントが何であるのかを把握します。イントラデイコンテキストでは、その日の市場構造がレンジ相場なのかトレンド相場なのかなど、当日の値動きの特徴を分析します。そして、マイクロコンテキストが実際のエントリーを行う実行レイヤーです。ここでは、リアルタイムのオーダーフロー、吸収、そしてトラップなどの細かな値動きを観察します。

機関投資家のトレードでは、これらすべてのコンテキストが互いに影響し合っています。マクロ、イントラデイ、マイクロという3つのレイヤーが同じ方向を示しているとき、トレードは統計的な優位性を獲得できます。一方で、これらのコンテキストが互いに矛盾している場合、たとえば1分足では売りシグナルが成立していても、その直下に上位足の強力なサポートゾーンが存在するようなケースでは、一見すると理想的なセットアップであっても、その優位性は大きく低下します。

ここで重要となるのが、ICT (Inner Circle Trader)のSmart Money Concepts (SMC)の考え方です。流動性ゾーンとは、機関投資家が大量の注文を配置している価格帯を指します。これらのゾーンは、等しい高値・安値(Equal Highs/Lows)、前日高値・安値、ロンドン市場やニューヨーク市場のセッション高値・安値、あるいは大きなラウンドナンバー付近に形成されることが一般的です。価格がこれらの流動性プールへ到達し、たとえば等しい高値を突破すると、近くのオーダーブロックや構造的なゾーンから反転するケースが多く見られます。

基本的な考え方は非常にシンプルです。価格はしばしば先に流動性を刈り取り、ストップを巻き込み、ブレイクアウトを狙う参加者を市場に引き込んだ後で、機関投資家が注文を配置している構造的な価格帯から反転する傾向があります。オーダーブロックとは通常、市場構造をブレイクするほど強いディスプレイスメント(急激な価格変動)の直前に形成された、最後の反対色のローソク足、あるいはその小さなクラスタとして定義されます。強気オーダーブロックであれば、市場構造を上方向へブレイクする力強い上昇の直前に形成された最後の陰線を指します。反対に、弱気オーダーブロックは、市場構造を下方向へブレイクする急落の直前に形成された最後の陽線です。

このアプローチをさらに強力なものにしているのが、プレミアムゾーン(割高価格帯)とディスカウントゾーン(割安価格帯)という概念です。スイングローからスイングハイへフィボナッチリトレースメントを描くと、50%ラインが均衡点となります。50%より下はディスカウントゾーン、50%より上はプレミアムゾーンです。強気相場では、ディスカウントゾーン内にある強気オーダーブロックで買いを狙います。一方、弱気相場では、プレミアムゾーン内にある弱気オーダーブロックで売りを狙うことが基本となります。

この考え方を踏まえ、私はこれらの重要なゾーンをチャート上で自動的に検出し、可視化することを目的とした「流動性ゾーンインジケータ」を開発しました。このインジケータの基礎となる考え方については、前回の記事「初心者からエキスパートへ:流動性インジケータの作成」で詳しく解説しています。その根底にあるのは、持ち合いの深さと、その後に続くインパルスブレイクアウトの大きさとの間には、測定可能な3:1の関係があるという重要な定量的知見です。

それでは、このオリジナルのインジケータの中核となるロジックを確認していきましょう。本稿で解説する内容は、このロジックを基盤として発展させたものです。

//+------------------------------------------------------------------+
//|                                         Liquidity Zone Indicator |
//|                                Copyright © 2026 Clemence Benjamin|
//|             https://www.mql5.com/ja/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 8
#property indicator_plots   4

//+------------------------------------------------------------------+
//| Input  Variables                                                 |
//+------------------------------------------------------------------+
input int           LookbackBars      = 1000;     //--- Number of bars to look back for zones
input double        RatioMultiplier   = 3.0;      //--- Ratio multiplier (3x)
input int           ExtendBars        = 50;       //--- Bars to extend rectangle right
input bool          FillRectangles    = true;     //--- Fill or just border?
input color         DemandZoneColor   = clrLimeGreen;
input color         SupplyZoneColor   = clrRed;
input uchar         ZoneOpacity       = 40;       //--- 0-255 transparency
input bool          ShowBuffers       = true;     //--- Show buffer lines on chart
input ENUM_LINE_STYLE BufferStyle     = STYLE_DASH; //--- Buffer line style
input int           BufferWidth       = 1;        //--- Buffer line width

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
double myPoint;
double Open[], Close[], High[], Low[];
datetime TimeArray[];

//--- Indicator Buffers
double DemandZoneHighBuffer[];
double DemandZoneLowBuffer[];
double SupplyZoneHighBuffer[];
double SupplyZoneLowBuffer[];
double DemandZoneSignalBuffer[];
double SupplyZoneSignalBuffer[];
double ZoneStartTimeBuffer[];
double ZoneEndTimeBuffer[];

//+------------------------------------------------------------------+
//| Candle range helper                                              |
//+------------------------------------------------------------------+
double CandleRange(const int i)
  {
   return(High[i] - Low[i]);
  }

//+------------------------------------------------------------------+
//| Draw a liquidity zone                                            |
//+------------------------------------------------------------------+
void DrawZone(const int base_idx, const bool isDemand, const int rates_total)
  {
   datetime start_time = TimeArray[base_idx];
   datetime end_time   = start_time + ExtendBars * PeriodSeconds();

   string obj_name = "LiqZone_" + IntegerToString(start_time);

   //--- Delete existing object if present
   if(ObjectFind(0, obj_name) >= 0)
     ObjectDelete(0, obj_name);

   //--- Determine zone top/bottom
   double top, bottom;
   if(isDemand)
     {
      top    = High[base_idx];
      bottom = Low[base_idx];
     }
   else
     {
      top    = High[base_idx];
      bottom = Low[base_idx];
     }

   //--- Create rectangle
   if(ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, start_time, top, end_time, bottom))
     {
      color zone_color = isDemand ? DemandZoneColor : SupplyZoneColor;
      ObjectSetInteger(0, obj_name, OBJPROP_COLOR, zone_color);
      ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, obj_name, OBJPROP_FILL, FillRectangles);
      ObjectSetInteger(0, obj_name, OBJPROP_BACK, true);
      ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, false);

      if(FillRectangles)
        {
         color fill_clr = (color)ColorToARGB(zone_color, ZoneOpacity);
         ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, fill_clr);
        }
     }

   //--- Optional buffers for visualization
   if(ShowBuffers && base_idx < rates_total)
     {
      if(isDemand)
        {
         DemandZoneHighBuffer[base_idx] = top;
         DemandZoneLowBuffer[base_idx]  = bottom;
        }
      else
        {
         SupplyZoneHighBuffer[base_idx] = top;
         SupplyZoneLowBuffer[base_idx]  = bottom;
        }

      ZoneStartTimeBuffer[base_idx] = start_time;
      ZoneEndTimeBuffer[base_idx]   = end_time;
     }
  }

//+------------------------------------------------------------------+
//| Calculates indicator values                                      |
//+------------------------------------------------------------------+
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[])
  {
   //--- Not enough bars to calculate
   if(rates_total < 5)
     return(0);

   //--- Copy data into series arrays
   int bars_to_process = MathMin(rates_total, 5000);
   CopyOpen(Symbol(), PERIOD_CURRENT, 0, bars_to_process, Open);
   CopyClose(Symbol(), PERIOD_CURRENT, 0, bars_to_process, Close);
   CopyHigh(Symbol(), PERIOD_CURRENT, 0, bars_to_process, High);
   CopyLow(Symbol(), PERIOD_CURRENT, 0, bars_to_process, Low);
   CopyTime(Symbol(), PERIOD_CURRENT, 0, bars_to_process, TimeArray);

   ArraySetAsSeries(Open, true);
   ArraySetAsSeries(Close, true);
   ArraySetAsSeries(High, true);
   ArraySetAsSeries(Low, true);
   ArraySetAsSeries(TimeArray, true);

   //--- Iterate over bars to detect zones
   for(int i = bars_to_process - 3; i >= 0; i--)
     {
      int base_idx    = i + 2;
      int impulse_idx = i + 1;

      double base_range    = CandleRange(base_idx);
      double impulse_range = CandleRange(impulse_idx);

      //--- Demand zone: both bars bullish, impulse breaks out
      if(Open[base_idx] < Close[base_idx] &&
         Open[impulse_idx] < Close[impulse_idx] &&
         impulse_range >= base_range * RatioMultiplier)
        {
         DrawZone(base_idx, true, rates_total);
        }

      //--- Supply zone: both bars bearish, impulse breaks out
      if(Open[base_idx] > Close[base_idx] &&
         Open[impulse_idx] > Close[impulse_idx] &&
         impulse_range >= base_range * RatioMultiplier)
        {
         DrawZone(base_idx, false, rates_total);
        }
     }

   //--- Return total number of processed bars
   return(rates_total);
  }
//+------------------------------------------------------------------+

ここで、これまでの内容を少し振り返ってみましょう。元のインジケータは、統計的な観察結果を自動化された客観的なツールへと落とし込んだという点で、大きな前進でした。その中核となるロジック、つまりベースとなるローソク足を特定し、その後に続くインパルスローソク足が所定の比率を満たしているかを検証する処理は、シンプルでありながら非常に効果的です。また、このインジケータが描画するゾーンは、機関投資家による取引が行われた可能性の高い重要な市場構造レベルを正確に可視化します。詳細については、本ソースコードを初めて構築した前回の記事のテストセクションを改めて参照していただくとよいでしょう。

しかし、実際のトレードツールとして見ると、このインジケータには本質的な限界があります。それは、あくまでも「受動的な観察者」であるという点です。機関投資家がどこでポジションを構築したのかという「戦場」は示してくれますが、その重要なゾーンへ価格が戻ってきた際に、いつエントリーすべきかというシグナルは提供してくれません。下位足でのプライスアクションを監視してエントリータイミングを判断する作業は依然としてトレーダー自身に委ねられており、複数のチャートを見続けなければならないため、精神的な負担が大きく、絶好の機会を逃してしまう原因にもなります。つまり、このインジケータは「どこで」には答えるが「いつ」には答えないのです。

この「ゾーンの特定」と「実際に行動できるシグナル」との間に存在するギャップこそ、多くのトレードツールが抱える課題です。高確率のシナリオを示すマップはあって、リアルタイムでエントリーを判断するためのガイダンスが不足しているのです。

本記事では、この問題を解決する完全なソリューションとして、「マルチタイムフレーム流動性ゾーンリアクションシステム」を設計・実装していきます。この強化版インジケータは、従来の実績あるベースインパルスロジックを用いて上位足の流動性ゾーンを検出するだけでなく、現在の下位足をリアルタイムで監視します。そして、価格がそのゾーンへ戻ってきたタイミングで、包み足、ピンバー、インサイドバーブレイクなどの反転パターンが確認されると、チャート上に矢印を描画するとともにアラートを発し、上位足の構造分析と下位足のエントリータイミングを組み合わせた、明確かつ実践的な売買シグナルを提供します。

この新しいアーキテクチャを実現するためには、従来とは異なる考え方が必要になります。従来のインジケータは単一のチャート上で独立して動作していましたが、新しいシステムでは複数のレイヤーを組み合わせて動作する構造を採用します。

  • ゾーン検出レイヤー:ユーザーが指定した上位足(例:H1)で動作し、従来のベースインパルスロジックを利用して流動性ゾーンを検出・保存します。
  • ビジュアルレイヤー:検出したゾーンを現在表示中のチャート上に永続的な矩形として描画し、どの時間足へ切り替えても常に確認できるようにします。
  • リアクション監視レイヤー:現在の下位足(例:M5)において、確定したすべてのローソク足を評価します。各バーの価格レンジが有効な上位足ゾーンと重なっているかを判定し、さらに反転パターンが確認された場合にのみ売買シグナルを生成します。

本記事を読み終える頃には、このようなマルチタイムフレーム対応システムをMQL5で構築する方法だけでなく、その背後にあるマルチタイムフレーム分析の本質についても理解できるようになるでしょう。また、機関投資家がどのように複数の時間軸を組み合わせて市場を分析しているのか、なぜ流動性ゾーンが自然なサポートレジスタンスとして機能するのか、そして時間足分析と精度の高い反転パターンを組み合わせることで、トレードの質をどのように向上させられるのかについても学ぶことができます。


実装

コンセプトを実際に動作するインジケータへと落とし込むには、綿密な設計と段階的な実装が欠かせません。ここでは、開発プロセス全体を順を追って解説し、それぞれのコンポーネントがシステム全体の中で果たす役割を説明します。これから構築する最終的なインジケータは、従来版を大きく発展させたものです。流動性ゾーンを検出するだけでなく、その後の価格の反応を継続的に監視し、トレードチャンスが発生した際には、分かりやすいビジュアルシグナルをチャート上に表示します。

ステップ1:インジケータの基本構成と入力パラメータの設定

すべてのMQL5インジケータは、プロパティ宣言と入力パラメータの定義から始まります。これらは、インジケータがチャート上でどのように表示されるか、どのデータを使用するか、そしてトレーダーがどのような項目をカスタマイズできるかを決定します。今回作成するマルチタイムフレームリアクションインジケータでは、上位足でのゾーン検出と、現在の時間足でのリアクション監視の両方を制御できる入力パラメータを用意する必要があります。

コードの最初のセクションでは、これらの基本構成を定義します。まず、インジケータをサブウィンドウではなくメインチャート上で動作するように宣言します。続いて、買いシグナル用と売りシグナル用の矢印を描画するための2つのインジケータバッファを確保し、それぞれに対応する2つのプロットを定義します。入力パラメータは、用途ごとに分かりやすく整理されています。最初に、上位足でのゾーン検出に関する設定(分析対象の時間足、過去に遡って解析するバー数、ゾーンを判定するための拡張率、そして矩形をどこまで延長して表示するか)を定義します。続いて、ゾーンの色や透明度、塗りつぶしの有無などの表示に関するカスタマイズ項目を設定し、最後に、シグナル矢印の色やサイズといったリアクションシグナルの表示設定を定義します。

//+------------------------------------------------------------------+
//|                                 Liquidity Zone Reaction Indicator|
//|                                Copyright © 2026 Clemence Benjamin|
//|             https://www.mql5.com/ja/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
#property version   "3.0"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

//--- Inputs for zone detection (higher timeframe)
input ENUM_TIMEFRAMES ZoneTimeframe   = PERIOD_H1;       // Timeframe for zone detection
input int             LookbackBars    = 1000;            // Number of bars on higher TF to look back
input double          RatioMultiplier = 3.0;             // Ratio Multiplier (3x)
input int             ExtendBars      = 50;              // Bars to extend rectangle right (on higher TF)
input bool            FillRectangles  = true;            // Fill or just border?
input color           DemandZoneColor = clrLimeGreen;    // Demand zone rectangle color
input color           SupplyZoneColor = clrTomato;       // Supply zone rectangle color
input uchar           ZoneOpacity     = 40;              // 0-255 transparency

//--- Inputs for reaction signals (current timeframe)
input color           DemandArrowColor = clrLimeGreen;   // Buy arrow color
input color           SupplyArrowColor = clrRed;         // Sell arrow color
input int             ArrowSize        = 1;              // Arrow width

//--- Arrow buffers
double BuyArrow[];
double SellArrow[];
//+------------------------------------------------------------------+

ステップ2:ゾーン構造体とグローバル変数の定義

このインジケータの中心となるのが、LiquidityZone構造体です。このカスタムデータ型は、1つのゾーンについて必要となるすべての情報をひとまとめに管理します。具体的には、価格に関する境界情報(高値と安値)、時間に関する境界情報(開始時刻と有効期限)、ゾーンの種類(需要ゾーンまたは供給ゾーン)、そしてすでにシグナルが発生済みかどうかを示す状態情報を保持します。ゾーン情報を動的配列として保存することで、複数の有効なゾーンを同時に管理できるようになります。それぞれのゾーンは独自のプロパティと状態を保持します。

//--- Current timeframe data arrays (for reversal patterns)
double Open[], Close[], High[], Low[];
datetime TimeArray[];

//--- Global variables
double    myPoint;
int       htfPeriodSeconds;               // period seconds of the higher timeframe
int       currentPeriodSeconds;           // current timeframe period in seconds
datetime  lastHtfUpdateTime = 0;          // last time zones were updated
datetime  lastAlertTime = 0;               // for alert throttling

//+------------------------------------------------------------------+
//| Zone structure                                                   |
//+------------------------------------------------------------------+
struct LiquidityZone
  {
   double            high;              // zone top price
   double            low;               // zone bottom price
   datetime          start_time;        // bar time where zone was formed
   datetime          expiry_time;       // time when zone expires (start + ExtendBars * htfPeriod)
   bool              demand;            // true = demand, false = supply
   bool              triggered;         // true if a signal already fired in this zone
  };

LiquidityZone zones[];          // dynamic array of currently active zones
//+------------------------------------------------------------------+

ステップ3:初期化とバッファ構成

OnInit()関数は、インジケータが最初に読み込まれた際に一度だけ実行される初期設定処理を担当します。ここでは、正しいポイント値の計算(5桁ブローカーへの対応を含む)、矢印バッファの設定、プロットプロパティの構成、そして時間関連変数の初期化などを行います。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   myPoint = Point();
   if(_Digits == 5 || _Digits == 3)
      myPoint *= 10;

//--- Set arrow buffers
   SetIndexBuffer(0, BuyArrow, INDICATOR_DATA);
   SetIndexBuffer(1, SellArrow, INDICATOR_DATA);

//--- Make arrow buffers series so index 0 = newest bar
   ArraySetAsSeries(BuyArrow, true);
   ArraySetAsSeries(SellArrow, true);

   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_ARROW);
   PlotIndexSetInteger(0, PLOT_ARROW, 233);                 // up arrow
   PlotIndexSetString(0, PLOT_LABEL, "Buy");
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, DemandArrowColor);
   PlotIndexSetInteger(0, PLOT_LINE_WIDTH, ArrowSize);

   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_ARROW);
   PlotIndexSetInteger(1, PLOT_ARROW, 234);                 // down arrow
   PlotIndexSetString(1, PLOT_LABEL, "Sell");
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, SupplyArrowColor);
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, ArrowSize);

   ArrayInitialize(BuyArrow, EMPTY_VALUE);
   ArrayInitialize(SellArrow, EMPTY_VALUE);

//--- Period seconds for time calculations
   currentPeriodSeconds = PeriodSeconds();
   htfPeriodSeconds = PeriodSeconds(ZoneTimeframe);
   if(htfPeriodSeconds <= 0)
      htfPeriodSeconds = 3600;       // fallback to 1 hour

//--- Initial update of zones (will be done in OnCalculate)
   lastHtfUpdateTime = 0;

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

ステップ4:クリーンアップとオブジェクト管理

適切なクリーンアップ処理は、プロフェッショナルなインジケータに不可欠です。インジケータがチャートから削除された際には、インジケータが作成したオブジェクト(今回の場合はゾーン矩形)も削除し、チャートを常に整理された状態に保つ必要があります。OnDeinit()関数はこの処理を自動的に実行し、DeleteAllZones()を呼び出して、名前が「LiqZone_」で始まるすべてのオブジェクトを削除します。

//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   DeleteAllZones();
  }

//+------------------------------------------------------------------+
//| Delete all drawn zone rectangles                                 |
//+------------------------------------------------------------------+
void DeleteAllZones()
  {
   for(int i = ObjectsTotal(0, 0, -1) - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i);
      if(StringFind(name, "LiqZone_") == 0)
         ObjectDelete(0, name);
     }
   ChartRedraw();
  }
//+------------------------------------------------------------------+

ステップ5:視覚的な確認のためのゾーン矩形描画

視覚的なフィードバックは、あらゆるトレードツールにおいて重要です。トレーダーは、ゾーンがどこに存在するのか、将来に向けてどこまで延長されているのか、そしてそれが需要エリアなのか供給エリアなのかを、一目で確認できる必要があります。DrawZone()関数はこの処理を担当し、適切なプロパティを設定した矩形オブジェクトを作成します。設定には、色、塗りつぶしスタイル、透明度、そして価格の動きを隠さないようにローソク足の背面へ配置する設定が含まれます。

//+------------------------------------------------------------------+
//| Draw a single zone rectangle                                     |
//+------------------------------------------------------------------+
void DrawZone(datetime start_time, datetime expiry_time,
              double price_top, double price_bottom, bool isDemand)
  {
   string obj_name = "LiqZone_" + IntegerToString(start_time) + "_" + IntegerToString(expiry_time);
   if(ObjectFind(0, obj_name) >= 0)
      ObjectDelete(0, obj_name);

   color zone_color = isDemand ? DemandZoneColor : SupplyZoneColor;

   if(ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0,
                   start_time, price_top, expiry_time, price_bottom))
     {
      ObjectSetInteger(0, obj_name, OBJPROP_COLOR, zone_color);
      ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, obj_name, OBJPROP_FILL, FillRectangles);
      ObjectSetInteger(0, obj_name, OBJPROP_BACK, true);           // behind price
      ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, false);

      if(FillRectangles)
        {
         color fill_clr = (color)ColorToARGB(zone_color, ZoneOpacity);
         ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, fill_clr);
        }
     }
  }
//+------------------------------------------------------------------+

ステップ6:上位足でのゾーン検出

このマルチタイムフレームアプローチの中心となるのが、UpdateZones()関数です。この関数は、選択した上位足で新しいバーが形成された際に呼び出されます。必要な価格データを取得し、ベースインパルスパターンをスキャンして、有効なパターンごとにLiquidityZone構造体を作成します。そして、それぞれに対応する矩形をチャート上へ描画します。

//+------------------------------------------------------------------+
//| Update zones from higher timeframe data                          |
//+------------------------------------------------------------------+
void UpdateZones()
  {
//--- Delete old rectangles and clear zone array
   DeleteAllZones();
   ArrayResize(zones, 0);

//--- Copy higher timeframe data
   double htf_open[], htf_close[], htf_high[], htf_low[];
   datetime htf_time[];
   int htf_bars = CopyOpen(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_open);
   if(htf_bars <= 0)
      return;
   CopyClose(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_close);
   CopyHigh(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_high);
   CopyLow(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_low);
   CopyTime(Symbol(), ZoneTimeframe, 0, LookbackBars, htf_time);

//--- Work with series (index 0 = most recent)
   ArraySetAsSeries(htf_open,  true);
   ArraySetAsSeries(htf_close, true);
   ArraySetAsSeries(htf_high,  true);
   ArraySetAsSeries(htf_low,   true);
   ArraySetAsSeries(htf_time,  true);

//--- Scan higher timeframe bars for zones
   for(int i = htf_bars - 5; i >= 2; i--)
     {
      int base_idx    = i + 2;
      int impulse_idx = i + 1;

      if(base_idx >= htf_bars)
         continue;

      double base_range    = htf_high[base_idx] - htf_low[base_idx];
      double impulse_range = htf_high[impulse_idx] - htf_low[impulse_idx];

      if(base_range <= 0)
         continue;
      //+------------------------------------------------------------------+

有効なゾーンが検出されると、新しいLiquidityZone構造体を作成します。そして、関連するデータ(高値、安値、開始時刻、終了時刻)を設定します。開始時刻はベースローソク足の時間から計算され、終了時刻は開始時刻にExtendBarsの値と上位足の秒単位の期間を掛けた値を加算して算出されます。その後、この構造体を動的配列へ追加し、対応する矩形をチャート上に描画します。

//--- Creating a new liquidity Zone                                            

if(htf_open[base_idx] < htf_close[base_idx] &&
   htf_open[impulse_idx] < htf_close[impulse_idx] &&
   impulse_range >= base_range * RatioMultiplier)
  {
   LiquidityZone z;
   z.high        = htf_high[base_idx];
   z.low         = htf_low[base_idx];
   z.start_time  = htf_time[base_idx];
   z.expiry_time = z.start_time + ExtendBars * htfPeriodSeconds;
   z.demand      = true;
   z.triggered   = false;

   int size = ArraySize(zones);
   ArrayResize(zones, size + 1);
   zones[size] = z;

   DrawZone(z.start_time, z.expiry_time, z.high, z.low, true);
  }
//+------------------------------------------------------------------+

ステップ7:エントリー確認のための反転パターン定義

ゾーンを特定することは、戦いの半分に過ぎません。もう半分は、価格がそのゾーンへ戻ってきた際に、いつ行動すべきかを判断することです。そのためには、方向転換の可能性を示す信頼性の高い反転パターンを用意する必要があります。このインジケータでは、強気と弱気の両方のシナリオに対応する3つの代表的な反転パターンを採用しています。それらは、包み足、ピンバー、そしてインサイドバーブレイクです。

//+------------------------------------------------------------------+
//| Bullish Engulfing Pattern                                        |
//| Detects bullish reversal when current candle engulfs previous    |
//| bearish candle body                                              |
//+------------------------------------------------------------------+
bool BullishEngulfing(int i)
  {
   if(Close[i] > Open[i] &&
      Close[i+1] < Open[i+1] &&
      Close[i] > Open[i+1] &&
      Open[i] < Close[i+1])
      return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Bearish Engulfing Pattern                                        |
//| Detects bearish reversal when current candle engulfs previous    |
//| bullish candle body                                              |
//+------------------------------------------------------------------+
bool BearishEngulfing(int i)
  {
   if(Close[i] < Open[i] &&
      Close[i+1] > Open[i+1] &&
      Open[i] > Close[i+1] &&
      Close[i] < Open[i+1])
      return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Bullish Pin Bar Pattern                                          |
//| Long lower wick indicating rejection of lower prices             |
//+------------------------------------------------------------------+
bool BullishPinBar(int i)
  {
   double body = MathAbs(Close[i] - Open[i]);
   double lower = MathMin(Open[i], Close[i]) - Low[i];
   if(lower > body * 2)
      return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Bearish Pin Bar Pattern                                          |
//| Long upper wick indicating rejection of higher prices            |
//+------------------------------------------------------------------+
bool BearishPinBar(int i)
  {
   double body = MathAbs(Close[i] - Open[i]);
   double upper = High[i] - MathMax(Open[i], Close[i]);
   if(upper > body * 2)
      return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Bullish Inside Bar Breakout                                      |
//| Inside bar structure followed by bullish breakout                |
//+------------------------------------------------------------------+
bool BullishInsideBreak(int i)
  {
   if(High[i] < High[i+1] && Low[i] > Low[i+1])
      if(Close[i] > High[i+1])
         return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Bearish Inside Bar Breakout                                      |
//| Inside bar structure followed by bearish breakout                |
//+------------------------------------------------------------------+
bool BearishInsideBreak(int i)
  {
   if(High[i] < High[i+1] && Low[i] > Low[i+1])
      if(Close[i] < Low[i+1])
         return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Demand Zone Reversal Detector                                    |
//| Combines bullish reversal patterns for demand zones              |
//+------------------------------------------------------------------+
bool DemandReversal(int i)
  {
   if(BullishEngulfing(i))
      return true;
   if(BullishPinBar(i))
      return true;
   if(BullishInsideBreak(i))
      return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Supply Zone Reversal Detector                                    |
//| Combines bearish reversal patterns for supply zones              |
//+------------------------------------------------------------------+
bool SupplyReversal(int i)
  {
   if(BearishEngulfing(i))
      return true;
   if(BearishPinBar(i))
      return true;
   if(BearishInsideBreak(i))
      return true;
   return false;
  }
//+------------------------------------------------------------------+

ステップ8:2段階方式によるOnCalculateロジック

OnCalculate関数は、これまでのすべての処理を統合する中心的な役割を担います。この関数では、過去のシグナル表示とリアルタイムでのアラート生成の両方に対応する、2段階方式の処理を実装します。この二重構成により、インジケータを読み込んだ際には過去に発生したすべてのシグナルを確認でき(バックテストやシステムの過去パフォーマンスを理解する上で重要です)、同時に新たなトレード機会が発生した際には、タイムリーなアラートを受け取ることができます。

//+------------------------------------------------------------------+
//| Calculates indicator values and updates zones                    |
//+------------------------------------------------------------------+
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[])
  {
   //--- Not enough bars to calculate
   if(rates_total < 5)
     return(0);

   //--- Copy current timeframe data into global arrays (as series)
   int barsNeeded = MathMin(rates_total, 5000);  // safe upper limit
   CopyOpen(Symbol(), PERIOD_CURRENT, 0, barsNeeded, Open);
   CopyClose(Symbol(), PERIOD_CURRENT, 0, barsNeeded, Close);
   CopyHigh(Symbol(), PERIOD_CURRENT, 0, barsNeeded, High);
   CopyLow(Symbol(), PERIOD_CURRENT, 0, barsNeeded, Low);
   CopyTime(Symbol(), PERIOD_CURRENT, 0, barsNeeded, TimeArray);

   ArraySetAsSeries(Open,      true);
   ArraySetAsSeries(Close,     true);
   ArraySetAsSeries(High,      true);
   ArraySetAsSeries(Low,       true);
   ArraySetAsSeries(TimeArray, true);

   //--- Update zones if a new higher timeframe bar appears
   datetime latestHtfTime = iTime(Symbol(), ZoneTimeframe, 0);
   if(latestHtfTime != lastHtfUpdateTime)
     {
      UpdateZones();
      lastHtfUpdateTime = latestHtfTime;
     }

   //--- further calculations continue here...

   return(rates_total);
  }
//+------------------------------------------------------------------+

最初のパスでは、完了済みのすべてのバーを古いものから新しいものへ順番にループ処理します。各バーについて、ゾーンの有効時間範囲内に存在しているか、その価格がゾーン内にあるか、そして対応する反転パターンが形成されているかを確認します。条件を満たすバーが見つかった場合、対応する矢印バッファに価格値を設定します。買いシグナルの場合はバーの安値の少し下、売りシグナルの場合はバーの高値の少し上に配置することで、チャート上に直接視覚的なマーカーを表示します。

//---------------------------------------------------------------
// 1) Loop over ALL completed bars (oldest to newest) and set arrows
//    This makes historical signals appear on the chart.
//---------------------------------------------------------------
for(int i = rates_total - 2; i >= 0; i--)   // i = bar index (0 = newest)
  {
   bool buySignal  = false;
   bool sellSignal = false;

// Check every active zone
   for(int z = 0; z < ArraySize(zones); z++)
     {
      // --- Time validity: bar must be within zone's lifetime ---
      if(TimeArray[i] < zones[z].start_time || TimeArray[i] > zones[z].expiry_time)
         continue;

      // --- Price inside zone? ---
      if(!PriceInsideZone(i, zones[z], High, Low))
         continue;

      // --- Reversal pattern? ---
      if(zones[z].demand && DemandReversal(i))
         buySignal = true;
      if(!zones[z].demand && SupplyReversal(i))
         sellSignal = true;
     }

// Set arrow (or EMPTY_VALUE if no signal)
   BuyArrow[i]  = buySignal  ? Low[i] - 5 * myPoint : EMPTY_VALUE;
   SellArrow[i] = sellSignal ? High[i] + 5 * myPoint : EMPTY_VALUE;
  }
//+------------------------------------------------------------------+

2つ目のパスでは、最新の確定済みバー(インデックス1)のみを対象に処理を行います。基本的な判定条件は1つ目のパスと同じですが、さらにtriggeredフラグを考慮します。これにより、一度ゾーンからアラートが発生した後は、同じゾーンから再度シグナルが生成されることはありません。この仕組みにより、価格がゾーン内に留まった状態で新しいバーが形成され続けても、同一ゾーンから繰り返しアラートが発生することを防ぎます。有効なシグナルが検出された場合は、関連情報を含むアラートが発生します。

//--------------------------------------------------------------------------
//     2)   Real-time alert handling (only for the most recent closed bars |
//   Uses the 'triggered' flag to avoid repeated alerts from the same zone.|
//--------------------------------------------------------------------------
int signal_bar = 1;   // last closed bar
if(signal_bar < rates_total)
  {
   for(int z = 0; z < ArraySize(zones); z++)
     {
      // Skip if zone already triggered
      if(zones[z].triggered)
         continue;

      // Time validity (using the bar's time, not TimeCurrent)
      if(TimeArray[signal_bar] < zones[z].start_time || TimeArray[signal_bar] > zones[z].expiry_time)
         continue;

      // Price inside?
      if(!PriceInsideZone(signal_bar, zones[z], High, Low))
         continue;

      // Reversal?
      if(zones[z].demand && DemandReversal(signal_bar))
        {
         zones[z].triggered = true;
         if(TimeArray[signal_bar] != lastAlertTime)
           {
            Alert("Demand Zone BUY Reaction ", Symbol(), " ", EnumToString(_Period));
            lastAlertTime = TimeArray[signal_bar];
           }
        }
      else
         if(!zones[z].demand && SupplyReversal(signal_bar))
           {
            zones[z].triggered = true;
            if(TimeArray[signal_bar] != lastAlertTime)
              {
               Alert("Supply Zone SELL Reaction ", Symbol(), " ", EnumToString(_Period));
               lastAlertTime = TimeArray[signal_bar];
              }
           }
     }
  }

return(rates_total);
  }
//+------------------------------------------------------------------+

ステップ9:ゾーン内価格判定

最後の要素は、指定されたバーの価格範囲がゾーンと重なっているかを確認する、シンプルながら重要なヘルパー関数です。この関数はOnCalculate()の処理全体で繰り返し呼び出されるため、正確性と効率性の両方が求められます。

//+------------------------------------------------------------------+
//| Check if a bar is inside a zone                                  |
//+------------------------------------------------------------------+
bool PriceInsideZone(int bar, const LiquidityZone &z,
                     const double &high[], const double &low[])
  {
   if(high[bar] >= z.low && low[bar] <= z.high)
      return true;
   return false;
  }
//+------------------------------------------------------------------+

これらすべてのコンポーネントが連携することで、機関投資家のゾーンを特定し、実際のトレード機会が発生するまで監視し、機会が訪れた際にはトレーダーへ通知する、包括的なマルチタイムフレーム流動性ゾーンリアクションインジケータが完成します。


テストと結果

マルチタイムフレームインジケータをテストする際には、ゾーン検出ロジックとリアクション監視機能の両方を検証するための体系的なアプローチが必要です。テスト手順では、過去データに対する精度、リアルタイムでの動作性能、そして例外的なケースへの対応を確認する必要があります。効率的かつ包括的な検証を行うために、本インジケータでは2つの補完的なテスト方法を採用します。それは、迅速な過去検証を行うためのストラテジーテスターと、リアルタイム動作を確認するための実際のチャート上での観察です。

EURUSD M5上でインジケータが動作している状態を示すストラテジーテスターのビジュアル表示

図1は、ストラテジーテスターが数か月分の過去の価格変動を数分間に圧縮し、迅速なパフォーマンス評価を可能にする様子を示しています。

テスト方法

ストラテジーテスターモード:ライブチャートでは、有意義な分析に必要な数のシグナルが蓄積されるまで、数日から数週間待つ必要があります。ストラテジーテスターを使用すると、数か月分の過去データを数分でシミュレーションできます。異なる期間を選択するだけで、トレンド相場、レンジ相場、高ボラティリティ、低ボラティリティなど、さまざまな市場環境においてインジケータがどのように機能したかを確認できます。この圧縮されたフィードバックループは、以下の確認に不可欠です。

  • 多様な市場フェーズにおいてゾーンが正しく検出されることの確認
  • 条件を満たすすべての過去バーにリアクション矢印が表示されることの確認
  • 特定の銘柄に合わせた入力パラメータ(例:RatioMultiplier)の調整
  • 実運用前にインジケータのロジックへの信頼性を高めること

ライブチャートモード(実環境での検証)ストラテジーテスターによって過去データへの信頼性を確認できる一方で、ライブテストではリアルタイムでの動作を検証します。

  • 新しいバーが形成された際にアラートが正しく発生すること
  • 最新の未確定バーをインジケータがどのように処理するか
  • 新しい価格データが到着してもゾーン矩形が正しく維持されること
  • 現在の市場環境における実際のシグナル発生タイミングと頻度

両方のアプローチは重要です。ストラテジーテスターは速度を提供し、ライブテストは確実性を提供します。以下では、それぞれのテスト手順と、その組み合わせによる結果を記録します。

テスト手順

パートA:ストラテジーテスター

1. MetaTrader 5のストラテジーテスターを開き、銘柄としてEURUSDを選択します。時間足はM5を指定し、大きな価格変動を含む過去期間(例:2020年1月〜2020年3月)を設定します。「ビジュアルモード」を有効にして、バー単位でインジケータの動作を確認します。テスター上で対象インジケータを選択します。

2. インジケータの入力設定を行います。ZoneTimeframeをPERIOD_H1(上位足分析)に設定し、EURUSDでのテスト用としてRatioMultiplierを2.25に設定します。その他の表示パラメータは必要に応じて調整します(図2は一般的な設定例を示しています)。

インジケータのプロパティ画面:入力パラメータ設定

図2:インジケータ入力パラメータ - EURUSD M5上でH1ゾーン検出を行う設定

3. ビジュアルバックテストを実行します。ローソク足の保ち合い後に、3倍比率の条件を満たす、またはそれを超えるインパルスバーが形成された重要な市場構造ポイントに、ゾーン矩形が表示されることを確認します。ゾーンは上位足のデータに基づいて描画されていますが、現在のM5チャート上に表示されます。これは、設計したマルチタイムフレーム表示が正常に機能していることを示しています。

4. シミュレーションが進行するにつれて、価格が既存のゾーンへ戻ってくる場面を確認します。価格が需要ゾーン(緑色の矩形)へ入り、強気の反転パターンを形成した場合、買い矢印(上向き矢印)が表示されます。価格が供給ゾーン(赤色の矩形)へ入り、弱気の反転パターンを形成した場合、売り矢印(下向き矢印)が表示されます。

5. シミュレーション完了後、チャート履歴を遡って確認します。条件が成立したすべてのバーに矢印が表示されていることを確認してください。これにより、2段階方式のOnCalculateロジックが過去シグナルを正しく生成していることを検証できます。この確認作業は、ライブチャート上で行う場合には数週間かかる可能性があります。

パートB:ライブチャート

1. 同じ入力設定で、ライブのEURUSD M5チャートへインジケータを適用します。数時間動作させ、インジケータの挙動を監視します。

2. ライブチャート上で、新しいゾーン形成とリアクションシグナルを確認します。図3は、H1ゾーンが明確に表示され、反転ポイントにM5リアクション矢印が描画されたライブチャートの例を示しています。

上位足ゾーンと下位足リアクション矢印を表示したライブチャート

図3:H1流動性ゾーン(矩形)と、ゾーン内部で反転パターンが発生した際に表示されるM5リアクション矢印

3. ターミナルウィンドウに表示されるアラートメッセージを確認します。ゾーン内で反転パターンを伴った新しいバーが確定すると、「Demand Zone BUY Reaction EURUSD PERIOD_M5」または「Supply Zone SELL Reaction EURUSD PERIOD_M5」の形式でアラートが発生します。図4は、ライブテスト中に取得されたこれらのアラート例を示しています。

インジケータによって生成された売買シグナルのアラート通知

図4:価格が反転パターンを伴ってゾーンへ到達した際に生成されたリアルタイム通知(長時間のライブ観察後に取得)

観察結果

2つのテスト方法を組み合わせた結果、過去シミュレーションとライブ観察の両方において、一貫した結果が得られました。

ゾーン検出精度:ストラテジーテスターとライブモードの両方において、上位足ゾーンは重要な市場構造レベルと一貫して一致しました。価格がこれらのゾーンへ再び到達した際には、多くの場合で反応が発生し、これらの領域が機関投資家にとって意味のある水準であることを裏付けました。EURUSDとXAUUSDでは、デフォルトの3.0比率ではなく2.25比率を使用しました。一方、Volatility 10 (1s) Indexのような、より高いボラティリティを持つ銘柄では、有効なゾーンを生成するために、より低い比率(2.0)が必要でした。これは、元の研究で示された結果とも一致しています。

シグナルの品質と頻度:すべてのゾーン到達がシグナルになるわけではありません。明確な反転パターンが確認された場合のみシグナルが生成されるため、発生頻度は比較的低くなりますが、確信度の高いシグナルとなり、過剰トレードの抑制につながります。ストラテジーテスターでは、EURUSDにおいて、このインジケータが重要な市場構造レベルでのみシグナルを生成することが確認されました。

過去シグナルの可視化:2段階方式の設計は、ストラテジーテスター上で完全に機能しました。すべての過去リアクションバーに矢印が表示され、バックテストが直感的に行えるようになりました。過去にシステムがどの地点でエントリーシグナルを出していたのかを即座に確認できることは、その動作を理解する上で大きな洞察を与えます。

アラートの信頼性:ライブテストでは、新しいシグナルに対してアラートが安定して発生し、不要な連続通知もありませんでした。これは、triggeredフラグ機構とlastAlertTimeによる制御によって実現されています。図4は、数時間のライブ観察中に取得された代表的なアラート例を示しています。

これらの観察結果は、元の研究から得られた重要な結論を改めて裏付けています。3:1比率は統計的に強力な基準値となりますが、最適なパフォーマンスを得るためには、対象となる銘柄に応じた調整が必要です。調整可能なRatioMultiplier入力によって、トレーダーは自身が取引する市場に合わせてインジケータを調整できます。これにより、固定された数式ではなく、柔軟に対応できるツールとして利用することが可能になります。

今回のテスト成功により、マルチタイムフレームアーキテクチャが意図した通りに機能することが確認されました。上位足のゾーンは検出・可視化され、下位足での価格反応が監視され、構造的な背景とエントリー確認条件の両方が一致した場合にのみ、明確なシグナルが生成されます。これにより、元々は受動的なゾーン検出インジケータだったものが、精度の高いエントリー判断を能動的に支援するツールへと進化しました。


結論

本記事を通じて、単純なゾーン検出インジケータを、包括的なマルチタイムフレームリアクションシステムへと発展させました。出発点となった基本概念は、「上位足の流動性ゾーンが市場の文脈を提供し、下位足の反転パターンがエントリータイミングを提供する」というものでした。そして、この考え方を中心に完全なMQL5実装を構築しました。

今回作成したツールは、機関投資家のトレード原則を取り入れています。それは、トップダウン分析、構造的な確認、そして正確なエントリータイミングです。このインジケーターは、機関投資家が注文を配置した可能性が高いゾーンを上位足から自動的に検出し、それらのゾーンを現在のチャート上へ直接描画します。そのため、どの時間足を表示していても重要なレベルを確認できます。さらに、すべてのバーを監視して価格反応の可能性を分析します。価格がゾーンへ入り、包み足、ピンバー、またはインサイドバーブレイクといった確認済みの反転パターンを形成すると、インジケーターは矢印を表示し、アラートを発生させます。これにより、明確で実行可能なシグナルが提供されます。

2段階方式の設計により、インジケータを読み込んだ際に過去のすべてのシグナルを確認できます。そのため、バックテストが直感的かつ有益になります。過去の価格履歴を遡ることで、システムがどの地点でエントリーシグナルを出したのか、それらのシグナルがどのような結果になったのか、そしてどのような市場環境でシステムが機能しやすいのか、あるいは苦戦するのかを正確に確認できます。

ここで実装した原則は、あらゆるトレード手法に応用できます。まず大きな市場の流れを把握し、機関投資家が活動している可能性の高い重要レベルを特定する。その後、価格がそのレベルへ戻ってくるのを待ち、行動する前に確認条件を探す。このトップダウンアプローチこそが、単一時間足だけで孤立したトレードを行う初心者と、プロフェッショナルなトレーダーを分ける要素です。

このインジケーターは、今後さまざまな方向へ拡張できます。明けの明星/宵の明星、はらみ模様、三本線反転など、追加の反転パターンも考えられます。また、フィボナッチリトレースメントを利用してプレミアムゾーン/ディスカウントゾーンを考慮することで、ゾーン検出ロジックをさらに強化することもできます。さらに、ロンドン市場やニューヨーク市場のオープンなど、特定の取引セッション中のみシグナルを生成するセッションフィルターを追加することも可能です。拡張の可能性は無限にあります。

ぜひこのインジケーターをダウンロードし、ご自身のチャートでテストして、あなた自身のトレードスタイルに合わせて調整してください。異なる時間足の組み合わせを試してみましょう。スイングトレードの場合はH4ゾーンとM15エントリー、スキャルピングの場合はM15ゾーンとM1エントリーといった組み合わせを検証できます。また、ゾーン発生頻度を調整するために比率マルチプライヤーを変更したり、ご自身の市場で有効な反転パターンに合わせて条件を変更したりすることも可能です。

ソースコードは、学習資料として、そして実践的なツールとして利用できるよう添付しました。コードを研究し、仕組みを理解し、そして自分自身のツールへと発展させてください。ご質問、ご意見、改善案がある場合は、ぜひ下記のコメント欄で共有してください。優れたトレードツールは、コミュニティからのフィードバックと協力によって進化していきます。

最後に覚えておいてください。市場とは、複数の時間軸にまたがって語られる1つの物語です。最後の一文だけを見るのではなく、物語全体を読むことを学びましょう。そうすれば、あなたのトレードはこれまでとは大きく変わるでしょう。

重要な学び

重要な学び 説明
マルチタイムフレームの整合性 上位足は市場構造の背景や機関投資家のバイアスを提供し、下位足は正確なエントリータイミングを提供します。両方が一致した場合、トレードの成功確率は大きく向上します。
流動性ゾーンは構造的レベルとして機能する 機関投資家が注文を配置するエリア(需要ゾーン/供給ゾーン)は、自然なサポート・レジスタンスレベルとして機能します。価格は継続または反転する前に、これらのゾーンへ戻ることがよくあります。
反転パターンによる確認 ゾーン内に価格が入っただけでは十分ではありません。そのレベルで機関投資家の反応が発生したことを示す確認パターン(包み足、ピンバー、インサイドバーブレイク)を待つ必要があります。
2段階シグナル処理 過去シグナルの表示とリアルタイムアラート生成を分離します。これにより、過去シグナルを分析用に確認できる一方、現在の不要なアラート連発を防止できます。
適切なバッファインデックス管理 矢印バッファは価格配列と一致させるためにArraySetAsSeriesで設定する必要があります。これを行わない場合、矢印が誤ったバーに表示されたり、過去シグナルが表示されなくなったりします。
オブジェクトベースの視覚的フィードバック ゾーン描画にOBJ_RECTANGLEを使用することで、時間足を変更してもゾーンを表示し続けることができます。また、色、塗りつぶし、透明度を設定することで視認性を最適化できます。
動的配列によるゾーン管理 ゾーン構造体の動的配列を使用することで、複数のアクティブゾーンを管理できます。それぞれのゾーンは独自のプロパティとtriggered状態を保持します。
時間によるゾーン期限管理 古くなったレベルがチャートを乱雑にしたり、誤ったシグナルを生成したりすることを防ぐため、ゾーンは一定数のバー(ExtendBars)経過後に無効化する必要があります。
アラートスロットリング lastAlertTimeを記録することで、同じバーに対する複数のアラート発生を防ぎ、明確で重複しない通知を実現します。
モジュール型パターン関数 各反転パターンを個別の関数として分離し、それらをDemandReversal/SupplyReversal内で組み合わせます。これにより、新しいパターンの追加やメンテナンスが容易になります。

添付ファイル

ファイル名 バージョン 種類 詳細
Liquidity_Zone_Reaction_Indicator.mq5 3.0 MQL5インジケータ ゾーン矩形、矢印シグナル、アラート機能を備えた完全なマルチタイムフレーム流動性ゾーンリアクションインジケータ


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/21581

添付されたファイル |
最後のコメント | ディスカッションに移動 (1)
M7f0OhpK4
M7f0OhpK4 | 17 5月 2026 において 10:25
本当にありがとうございます。近いうちにご意見をお聞かせいただけるのを楽しみにしています。
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MetaTrader 5における低頻度定量戦略(第1回):OLAP対応データストアの構築 MetaTrader 5における低頻度定量戦略(第1回):OLAP対応データストアの構築
Parquetストレージ、Hive形式のパーティション、そしてDuckDBを基盤とした、定量分析のための実用的なデータパイプラインについて説明します。選択したSQLiteテーブルをParquetへ移行する方法、市場データをデータソース、銘柄、時間足、日付によって構造化する方法、そしてSQLウィンドウ関数を使用してそれをクエリする方法について詳しく説明します。ゴールデンクロスの例では、銘柄横断的なフォワードリターン評価を示します。付属するPythonスクリプトでは、データのダウンロード、変換、実行を処理します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
バックテスト過剰適合を防ぐ統合検証パイプライン ― V-in-V・CPCV・CSCVによる堅牢な戦略検証 バックテスト過剰適合を防ぐ統合検証パイプライン ― V-in-V・CPCV・CSCVによる堅牢な戦略検証
金融時系列データに対して標準的なウォークフォワード法やk分割交差検証が、なぜ実際以上に良好な結果を示してしまうのかを解説します。そのうえで、この問題を解決するための実践的な手法を紹介します。V-in-V (Validation-within-Validation)は、データを厳密に分割し、アンカー型ウォークフォワードによる検証を実施します。CPCV (Combinatorially Purged Cross-Validation)は、情報漏洩を防ぐために-パージとエンバーゴを適用し、複数の検証パスの結果を統合して評価します。さらにCSCV (Combinatorially Symmetric Cross-Validation)は、バックテスト過剰適合確率(PBO, Probability of Backtest Overfitting)を定量的に測定します。これらを組み合わせることで、異なる市場レジームに対する戦略の頑健性と、戦略選択の信頼性を整合的な評価フレームワークで評価できるようになります。