English
preview
MQL5入門(第15回):初心者のためのカスタムインジケーター作成ガイド(IV)

MQL5入門(第15回):初心者のためのカスタムインジケーター作成ガイド(IV)

MetaTrader 5トレーディングシステム |
31 0
Israel Pelumi Abioye
Israel Pelumi Abioye

はじめに

「MQL5入門」の連載へようこそ。 この記事は、これまでの回で学んできたアイデアや技術を土台にして構成されており、まったく新しいスタートというよりも、自然な続きとして感じられるでしょう。ここまででMQL5の基礎をしっかりと理解できているはずです。今回はその知識をさらに発展させ、より実践的で興味深いカスタムインジケーターを開発していきます。

MQL5のスキルは、実際に取り組んだプロジェクトの質によって決まります。そのため、本連載では常にプロジェクトベースのアプローチを採用しています。それは自ら学び、成長するための最も効果的な方法です。今回は、トレンドを認識し、構造のブレイクを検出し、売買シグナルを生成するインジケーターを作成します。シグナルにはエントリーポイント、ストップロス、複数のテイクプロフィット水準も含まれており、テストと発展が可能な包括的な戦略を構築できます。この記事では、プライスアクションの概念を用いて、MQL5でカスタムインジケーターを設計する方法を学びます。トレンドフォロー戦略を作成するために、高値更新(HH)、安値切り上げ(HL)、高値切り下げ(LH)、安値更新(LL)といった、重要な市場構造をどのように認識するかを理解していきます。

本記事では、以下の内容について学びます。

  • プライスアクションインジケーターの作成方法
  • 安値(L)、高値(H)、安値切り上げ(HL)、高値更新(HH)、安値更新(LL)、高値切り下げ(LH)といった重要ポイントを認識し、強気/弱気トレンドの構造を理解する
  • トレンドの重要ポイントに基づいてプレミアムゾーンとディスカウントゾーンを描画し、50%リトレースメントラインを表示する
  • 強気トレンドのセットアップにおいて、リスクリワード比を使って潜在的な利益目標を計算する方法
  • トレンド構造に基づいて、エントリーポイント、ストップロス(SL)、複数のテイクプロフィット(TP)レベルを計算・表示する方法


1. プロジェクトの準備

1.1. インジケーターの仕組み

このインジケーターは、安値(L)、高値(H)、安値切り上げ(HL)、高値更新(HH)を識別して上昇トレンドを示し、買いシグナルを生成します。HLとHHの間の50%リトレースメントレベルを特定し、HHを上抜ける構造がブレイクされた際にエントリーします。50%リトレースメントはストップロスとして使用され、テイクプロフィット1はリスクリワード比1:1、テイクプロフィット2は1:2を目標とします。

図1:上昇トレンド

売りシグナルとして下降トレンドを識別するために、インジケーターはまずH、L、LH、LLを特定します。次に、LHとLLの間の50%リトレースメントを計算します。エントリーはLLを下抜けたブレイクでおこない、ストップロスは50%の位置に設定します。テイクプロフィット1はリスクリワード比1:1、テイクプロフィット2は1:2を目標とします。

図2:下降トレンド


2. プライスアクションインジケーターの構築

すべての取引戦略はインジケーターに変換できます。まだ可視化されていないだけです。需要と供給、価格変動、サポートとレジスタンスなど、一定のルールに従うものであれば何でもコードにしてチャート上に表示することが可能です。ここで登場するのがMQL5です。アルゴリズムトレーダーにとって、MQL5は最も優れた、かつ扱いやすいプログラミング言語のひとつであり、どんな取引ロジックも有用かつ視覚的にわかりやすいツールに変換することを可能にします。この章では、価格の動きを分析するインジケーターの開発を開始します。高値(H)、安値(L)、高値更新(HH)、安値更新(LL)といった市場構造を認識し、それに基づいてエントリー、ストップロス、テイクプロフィットを含む有益な売買シグナルを生成します。

第1章では、このプロジェクトの目的と、インジケーターがトレンドをどのように検出し、構造のブレイクを認識し、取引シグナル(エントリー、SL、TP)を生成するかを概説しました。ここからは、MQL5でそのロジックを実際にコードへと落とし込んでいく段階です。前章で説明したロジックを元に、一歩ずつ実装を進めていきます。

2.1.高値と安値の識別

プライスアクションインジケーターを作成するうえで最初のステップは、スイングハイとスイングローを見つけることです。これらの重要な転換点は、トレンド構造を識別する助けになります。MQL5では、現在のローソク足の高値または安値を、直前と直後のローソク足と比較することでそれを特定できます。この手法を使えば、HH、HL、LL、LHといった構造上のパターンを識別できるようになります。これらの構造変化は、トレンドの継続や転換を判断するための基礎になります。

//+------------------------------------------------------------------+
//| FUNCTION FOR SWING LOWS                                          |
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(low[index] > low[index - i] || low[index] > low[index + i])
         return false; 
     }
   return true; 
  }


//+------------------------------------------------------------------+
//| FUNCTION FOR SWING HIGHS                                         |
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high[], int index, int lookback)
  {
   for(int i = 1; i <= lookback; i++)
     {
      if(high[index] < high[index - i] || high[index] < high[index + i])
         return false; 
     }
   return true; 
  }

これら2つの関数は前回の記事で使用されており、スイングハイとスイングローの両方を識別するのに役立ちます。

2.2. 強気トレンド

市場構造を用いて、このインジケーターはまず上昇トレンドが存在するかを確認し、その後に買いシグナルを示します。これを達成するために識別すべき主な価格ポイントは、L、H、HL、HHです。このパターンは強気トレンドを示しており、買い手が優勢であり、市場が上昇を続ける可能性が高いことを意味します。このパターンの確認後、インジケーターは有効な買いシグナルを発する準備を整えます。

図3:強気トレンド

// CHART ID
long chart_id = ChartID();

// Input parameters
input int  LookbackBars = 10;   // Number of bars to look back/forward for swing points
input int  bars_check   = 1000; // Number of bars to check for swing points
input bool show_bullish = true; //Show Buy Signals

// Variables for Bullish Market Structure

double L;              // Low: the starting low point in the up trend
datetime L_time;       // Time of the low
string L_letter;       // Label for the low point (e.g., "L")

double H;              // High: the first high after the low
datetime H_time;       // Time of the high
string H_letter;       // Label for the high point (e.g., "H")

double HL;             // Higher Low: the next low that is higher than the first low
datetime HL_time;      // Time of the higher low
string HL_letter;      // Label for the higher low point (e.g., "HL")

double HH;             // Higher High: the next high that is higher than the first high
datetime HH_time;      // Time of the higher high
string HH_letter;      // Label for the higher high point (e.g., "HH")

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

   ObjectsDeleteAll(chart_id);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   if(show_bullish == true) // Check if the bullish trend is to be displayed
     {
      if(rates_total >= bars_check) // Ensure enough bars are available for analysis
        {
         // Loop through the price data starting from a certain point based on bars_check and LookbackBars
         for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++)
           {
            // Check if the current bar is a swing low
            if(IsSwingLow(low, i, LookbackBars))
              {
               // Store the values for the swing low
               L = low[i];
               L_time = time[i];
               L_letter = StringFormat("Low%d", i);

               // Loop through further to find a swing high after the low
               for(int j = i; j < rates_total - LookbackBars; j++)
                 {
                  // Check if the current bar is a swing high and occurs after the identified swing low
                  if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time)
                    {
                     // Store the values for the swing high
                     H = high[j];
                     H_time = time[j];
                     H_letter = StringFormat("High%d", j);

                     // Loop further to find a higher low after the swing high
                     for(int k = j; k < rates_total - LookbackBars; k++)
                       {
                        // Check if the current bar is a swing low and occurs after the swing high
                        if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time)
                          {
                           // Store the values for the higher low
                           HL = low[k];
                           HL_time = time[k];
                           HL_letter = StringFormat("Higher Low%d", j);

                           // Loop further to find a higher high after the higher low
                           for(int l = j ; l < rates_total - LookbackBars; l++)
                             {
                              // Check if the current bar is a swing high and occurs after the higher low
                              if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time)
                                {
                                 // Store the values for the higher high
                                 HH = high[l];
                                 HH_time = time[l];
                                 HH_letter = StringFormat("Higher High%d", l);

                                 // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High
                                 if(L < H && HL < H && HL > L && HH > H)
                                   {
                                    // Create and display text objects for Low, High, Higher Low, and Higher High on the chart
                                    ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L);
                                    ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L");
                                    ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15);

                                    ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H);
                                    ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H");
                                    ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15);

                                    ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL);
                                    ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL");
                                    ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15);

                                    ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH);
                                    ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH");
                                    ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15);
                                   }

                                 break; // Exit the loop once the pattern is found
                                }
                             }

                           break; // Exit the loop once the higher low is found
                          }
                       }

                     break; // Exit the loop once the higher high is found
                    }
                 }
              }
           }
        }
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

出力

図4:上昇トレンドのスイング

説明

コードの冒頭で設定された入力パラメータは、市場構造を解析するために使われます。スイングハイやスイングローを特定し、現在の市場における妥当性を保証するために、LookbackBarsは現在のバーの前後で評価するバーの数を指定します。一方で、bars_checkは解析対象となるバーの総数を制御し、最大1000本のバーを対象に強気パターンの探索を可能にします。

bars_checkの値が大きいほどより多くのデータを考慮するため、より多くの計算リソースを要しますが、広範囲のデータを用いて場所を検出できます。bool型の入力パラメータ「show_bullish」は、買いシグナル(強気シグナル)を表示するかどうかを制御します。show_bullishがtrueに設定されている場合のみ、価格の動きを解析し、スイングポイントに基づく強気の市場構造を判定します。falseの場合は、条件を満たしても強気構造はチャートに表示されません。

インジケーターのロジックで最初におこなわれる処理は、「show_bullish == true」のチェックです。これにより、買いシグナルの表示を希望した場合のみ強気構造の判定がおこなわれます。続いて、十分な価格データがあるかを確認します。条件式「if (rates_total >= bars_check)」により、必要なバー数が揃っているかが検証され、不足している場合は解析をスキップしてエラーを防ぎます。条件を満たした場合、価格データをループで走査しスイングポイントを探します。

外側のループ「i」は最新のバーから過去に向かって価格データを調べ、有効なスイングローを検出します。IsSwingLow関数は現在のバーがLookbackBarsの範囲内で最も安いかを判定し、スイングローを特定します。スイングローが見つかった場合、その価格と時刻が変数LとL_timeに記録され、強気パターン探索の次の段階の基礎となります。  スイング安値の後、2つ目の「j」ループでスイングハイを探します。IsSwingHigh関数を用い、スイングローの後に続くスイングハイが見つかった場合、その値と時刻がHとH_timeに保存されます。これで強気市場構造の最初の部分、安値と高値が揃います。

次に、3つ目の「k」ループが安値切り上げ(HL)を探索します。再びIsSwingLow関数を使い、最初の安値Lよりも高い安値が検出されると、その価格と時刻がHLとHL_timeに更新されます。このHLの発見後、4つ目の「l」ループでさらに高値更新(HH)を探します。これが見つかると、その値と時刻はHHとHH_timeに記録されます。4つの重要なポイント(L、H、HL、HH)が揃った後、次の条件で正当な強気パターンかを判定します。最初の安値は最初の高値より低く、それより高い安値は最初の高値より低く、それより高い安値は最初の安値より高く、それより高い高値は最初の高値より高くなければなりません。つまり「L < HかつHL < HかつHL > LかつHH > H」です。これにより、順番通りに高値更新・安値切り上げのパターンが成立していることを確認し、強気トレンドであることが保証されます。

条件を満たすと、プログラムはチャート上にテキストオブジェクトを作成して、特定した各ポイントを目立つように表示します。L、H、HL、HHの各ポイントが対応する時間と価格にラベル付けされます。ObjectCreate関数でテキストオブジェクトを生成し、ObjectSetInteger関数とObjectSetString関数でフォントサイズや色などの設定をおこないます。これにより、ユーザーはチャート上で強気の市場構造を視覚的に容易に認識できます。まとめると、このプログラムの目的は、価格データから高値更新・安値切り上げのパターンを見つけ出し、強気市場構造かどうかを判定することにあります。これは、あらかじめ定められたバーの範囲内でスイングポイントを検出し、関連する情報(価格と時間)を記録し、その構造が期待されるパターンになっているかどうかを確認することで実現されます。そしてそのパターンが正当であると判断された場合、チャート上に視覚的に表示されるようになっています。すべての処理は、ユーザーが調整可能な入力パラメータによって制御されています。

2.2.1. HLからHHへのプレミアムおよびディスカウントレベルのマッピング

強気の市場構造におけるスイングポイント — 安値(L)、高値(H)、安値切り上げ(HL)、高値更新(HH)が特定されたら、次のステップは、HLからHHまでボックスを描くことです。このボックスは、これら2つの重要なスイングポイントの間の価格の動きを視覚的に示します。この範囲を計算し、その50%リトレースメントレベル(価格ゾーンを分ける重要な境界線)を求めたうえで、そのラインを境に範囲を半分に分割します。

この分割により、「プレミアムゾーン」と「ディスカウントゾーン」という用語が定義されます。50%ラインより下は「ディスカウントゾーン」となり、購入に適した割安な価格帯と見なされます。一方、50%より上は「プレミアムゾーン」となり、相対的に「割高」な価格帯を意味します。多くの取引手法では、リスクとリワードの最適化のために、通常ディスカウントゾーンでの買いが選ばれます。しかし、本インジケーターにおいては、やや異なる戦略が採用されます。

図5:プレミアムとディスカウント

この場合、私たちが関心を持つのは、市場がプレミアムゾーンを上回って取引するか、高値更新の構造をブレイクしたときのみです。このパターンは、強気の市場構造が依然として有効であり、価格がさらに上昇する可能性が高いことを示しています。トレンドに沿った取引を維持し、押し目や反転局面での誤ったエントリーを避けるためには、高値更新またはプレミアムゾーンの上抜けを待つことが推奨されます。

// CHART ID
long chart_id = ChartID();

// Input parameters
input int  LookbackBars = 10;   // Number of bars to look back/forward for swing points
input int  bars_check   = 1000; // Number of bars to check for swing points
input bool show_bullish = true; //Show Buy Signals

// Variables for Bullish Market Structure

double L;              // Low: the starting low point in the up trend
datetime L_time;       // Time of the low
string L_letter;       // Label for the low point (e.g., "L")

double H;              // High: the first high after the low
datetime H_time;       // Time of the high
string H_letter;       // Label for the high point (e.g., "H")

double HL;             // Higher Low: the next low that is higher than the first low
datetime HL_time;      // Time of the higher low
string HL_letter;      // Label for the higher low point (e.g., "HL")

double HH;             // Higher High: the next high that is higher than the first high
datetime HH_time;      // Time of the higher high
string HH_letter;      // Label for the higher high point (e.g., "HH")

// Variables for Premium and Discount
string pre_dis_box;     // Name/ID for the premium-discount zone box (rectangle object on chart)
double lvl_50;          // The price level representing the 50% retracement between Higher Low and Higher High
string lvl_50_line;     // Name/ID for the horizontal line marking the 50% level


//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

   ObjectsDeleteAll(chart_id);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   if(show_bullish == true) // Check if the bullish trend is to be displayed
     {
      if(rates_total >= bars_check) // Ensure enough bars are available for analysis
        {
         // Loop through the price data starting from a certain point based on bars_check and LookbackBars
         for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++)
           {
            // Check if the current bar is a swing low
            if(IsSwingLow(low, i, LookbackBars))
              {
               // Store the values for the swing low
               L = low[i];
               L_time = time[i];
               L_letter = StringFormat("Low%d", i);

               // Loop through further to find a swing high after the low
               for(int j = i; j < rates_total - LookbackBars; j++)
                 {
                  // Check if the current bar is a swing high and occurs after the identified swing low
                  if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time)
                    {
                     // Store the values for the swing high
                     H = high[j];
                     H_time = time[j];
                     H_letter = StringFormat("High%d", j);

                     // Loop further to find a higher low after the swing high
                     for(int k = j; k < rates_total - LookbackBars; k++)
                       {
                        // Check if the current bar is a swing low and occurs after the swing high
                        if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time)
                          {
                           // Store the values for the higher low
                           HL = low[k];
                           HL_time = time[k];
                           HL_letter = StringFormat("Higher Low%d", j);

                           // Loop further to find a higher high after the higher low
                           for(int l = j ; l < rates_total - LookbackBars; l++)
                             {
                              // Check if the current bar is a swing high and occurs after the higher low
                              if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time)
                                {
                                 // Store the values for the higher high
                                 HH = high[l];
                                 HH_time = time[l];
                                 HH_letter = StringFormat("Higher High%d", l);

                                 // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High
                                 if(L < H && HL < H && HL > L && HH > H)
                                   {
                                    // Create and display text objects for Low, High, Higher Low, and Higher High on the chart
                                    ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L);
                                    ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L");
                                    ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15);

                                    ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H);
                                    ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H");
                                    ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15);

                                    ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL);
                                    ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL");
                                    ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15);

                                    ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH);
                                    ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH");
                                    ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen);
                                    ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15);


                                    // Calculate the 50% retracement level between the Higher Low and Higher High
                                    lvl_50 = HL + ((HH - HL)/2);

                                    // Generate unique names for the premium-discount box and the 50% level line using the current loop index
                                    pre_dis_box = StringFormat("Premium and Discount Box%d", i);
                                    lvl_50_line = StringFormat("Level 50 Line%d", i);

                                    // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High
                                    ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH);

                                    // Create a trend line (horizontal line) marking the 50% retracement level
                                    ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50);

                                    // Set the color of the premium-discount box to dark green
                                    ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen);

                                    // Set the color of the 50% level line to dark green
                                    ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen);

                                    // Set the width of the premium-discount box for better visibility
                                    ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2);

                                    // Set the width of the 50% level line for better visibility
                                    ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2);


                                   }

                                 break; // Exit the loop once the pattern is found
                                }
                             }

                           break; // Exit the loop once the higher low is found
                          }
                       }

                     break; // Exit the loop once the higher high is found
                    }
                 }
              }
           }
        }
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

出力


図6:チャート上のプレミアムとディスカウント

説明

このコードは、50%リトレースメントレベル(安値引き上げ(HL)と高値更新(HH)の中間点)を算出します。この50%ラインを基準に、プレミアムゾーン(上側)とディスカウントゾーン(下側)が分かれます。HLとHHの中間点は、式「lvl_50 = HL + ((HH - HL)/2);」を使用して決定されます。次に、ループインデックス「i」が、文字列変数pre_dis_boxとlvl_50_lineの名前に含まれます。これらの文字列は、チャート上に描画する視覚要素の固有の識別子として機能します。インデックス「i」を名前に含めることで、各描画が一意になり、ループ内で以前の描画が置き換えられないようになります。

次の行で、「ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH);」によって、チャート上に矩形が作成されます。これは、安値引き上げから高値更新までの移動を視覚的に示すもので、トレーダーが直近の強気スイングの範囲を一目で確認できるようにします。矩形の終点は、HHの価格を使い、将来のバー(l + LookbackBars)に固定されます。始点はHLの時間と価格に固定されています。これにより、ボックスが将来方向に延長され、チャート上に明確に表示されるようになります。

続いて、「ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50);」によって、チャート全体に50%レベルの水平線が描画されます。この水準は、インジケーターのロジックにおいて非常に重要で、市場がプレミアムゾーン上、つまり直近の上昇スイングの50%以上で取引されているかを判断する基準となります。ObjectSetInteger関数を使って、ボックスとラインの色をclrDarkGreenに設定し、太さ(幅)を2に指定することで、視認性が高まるようにしています。最後に、買いシグナルが有効と見なされるには、価格が HH(高値更新)を完全に上抜ける必要があります。つまり、プレミアムゾーン全体を超えてクローズしなければなりません。言い換えれば、明確に強気の市場構造が継続しており、前回のスイングハイ(HH)を超えている状況でのみ、買いを検討するということです。 

2.2.2. エントリーポイント、ストップロス、テイクプロフィットの表示

HLとHH間のプレミアムおよびディスカウントゾーンが正しくマークされたら、次のステップは、買いシグナルの可能性を見つけることです。正当な買いエントリーの鍵は、強気のブレイクアウトバーがHHを上抜けるのを待つことです。これは、市場が依然として力強く上昇していることを示します。

しかし、HHを一時的に上回るだけでは不十分です。その強気のバーがHHより上でクローズすることによってのみ、ブレイクアウトが本物であることが確認されます。価格がHHの上で終値を付けたという事実は、買いの需要が継続しており、価格がさらに上昇する可能性が高いことを意味します。エントリーポイントは、HHを上抜けてクローズしたその強気バーの終値に設定されます。これにより、「市場は十分な強さを示した」と判断し、安心して取引に参加することができます。

ストップロス(SL)は、HLとHHの中間点である50%ライン(lvl_50)に設定されます。これは、市場が反転する可能性に対する保険として機能します。また、価格がディスカウントゾーン(50%より下)へ戻るような動きを見せた場合、市場の勢いが弱まった、あるいは心理が変化したサインと見なされるため、この位置にSLを置くことでリスクを制御しています。 テイクプロフィット(TP)レベルは、リスクリワード比(R:R)に基づいて設定されます。最初のテイクプロフィット(TP1)の利益目標は、エントリーポイントとストップロスとの距離と同じであり、1:1のリスクリワード比となります。2つ目のテイクプロフィット(TP2)は、エントリーからSLまでの距離の2倍の位置に設定され、1:2のリスクリワード比となります。 これらの2つのTP設定により、トレーダーはTP1で部分的に利益を確定しつつ、市場がさらに上昇する可能性がある場合にはポジションの一部を保有し続けるといった柔軟な対応が可能になります。

// Variables for Entry, Stop Loss, and Take Profit
string entry_line;        // Line object to represent the entry point on the chart
string entry_txt;         // Text object for displaying "BUY" at the entry point
double lvl_SL;            // Stop Loss level (set at the 50% retracement level)
string lvl_sl_line;       // Line object for representing the Stop Loss level
string lvl_sl_txt;        // Text object for labeling the Stop Loss level
double TP1;               // Take Profit 1 level (1:1 risk-reward ratio)
double TP2;               // Take Profit 2 level (1:2 risk-reward ratio)
string lvl_tp_line;       // Line object for representing the Take Profit 1 level
string lvl_tp2_line;      // Line object for representing the Take Profit 2 level
string lvl_tp_txt;        // Text object for labeling the Take Profit 1 level
string lvl_tp2_txt;       // Text object for labeling the Take Profit 2 level
string buy_object;        // Arrow object to indicate the Buy signal on the chart
if(show_bullish == true) // Check if the bullish trend is to be displayed
  {
   if(rates_total >= bars_check) // Ensure enough bars are available for analysis
     {
      // Loop through the price data starting from a certain point based on bars_check and LookbackBars
      for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++)
        {
         // Check if the current bar is a swing low
         if(IsSwingLow(low, i, LookbackBars))
           {
            // Store the values for the swing low
            L = low[i];
            L_time = time[i];
            L_letter = StringFormat("Low%d", i);

            // Loop through further to find a swing high after the low
            for(int j = i; j < rates_total - LookbackBars; j++)
              {
               // Check if the current bar is a swing high and occurs after the identified swing low
               if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time)
                 {
                  // Store the values for the swing high
                  H = high[j];
                  H_time = time[j];
                  H_letter = StringFormat("High%d", j);

                  // Loop further to find a higher low after the swing high
                  for(int k = j; k < rates_total - LookbackBars; k++)
                    {
                     // Check if the current bar is a swing low and occurs after the swing high
                     if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time)
                       {
                        // Store the values for the higher low
                        HL = low[k];
                        HL_time = time[k];
                        HL_letter = StringFormat("Higher Low%d", j);

                        // Loop further to find a higher high after the higher low
                        for(int l = j ; l < rates_total - LookbackBars; l++)
                          {
                           // Check if the current bar is a swing high and occurs after the higher low
                           if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time)
                             {
                              // Store the values for the higher high
                              HH = high[l];
                              HH_time = time[l];
                              HH_letter = StringFormat("Higher High%d", l);

                              // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High
                              if(L < H && HL < H && HL > L && HH > H)
                                {
                                 // Create and display text objects for Low, High, Higher Low, and Higher High on the chart
                                 ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L);
                                 ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L");
                                 ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen);
                                 ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15);

                                 ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H);
                                 ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H");
                                 ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen);
                                 ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15);

                                 ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL);
                                 ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL");
                                 ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen);
                                 ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15);

                                 ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH);
                                 ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH");
                                 ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen);
                                 ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15);


                                 // Calculate the 50% retracement level between the Higher Low and Higher High
                                 lvl_50 = HL + ((HH - HL)/2);

                                 // Generate unique names for the premium-discount box and the 50% level line using the current loop index
                                 pre_dis_box = StringFormat("Premium and Discount Box%d", i);
                                 lvl_50_line = StringFormat("Level 50 Line%d", i);

                                 // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High
                                 ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH);

                                 // Create a trend line (horizontal line) marking the 50% retracement level
                                 ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50);

                                 // Set the color of the premium-discount box to dark green
                                 ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen);

                                 // Set the color of the 50% level line to dark green
                                 ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen);

                                 // Set the width of the premium-discount box for better visibility
                                 ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2);

                                 // Set the width of the 50% level line for better visibility
                                 ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2);


                                 for(int m = l; m < rates_total-1; m++)
                                   {

                                    if(close[m] > open[m] && close[m] > HH && time[m] >= time[l+LookbackBars])
                                      {

                                       TP1 = close[m] + (close[m] - lvl_50);
                                       TP2 = TP1 + (close[m] - lvl_50);

                                       entry_line = StringFormat("Entry%d", m);
                                       lvl_sl_line = StringFormat("SL%d", m);
                                       lvl_tp_line =  StringFormat("TP%d", m);
                                       lvl_tp2_line =  StringFormat("TP 2%d", m);

                                       ObjectCreate(chart_id,entry_line,OBJ_TREND,0,HL_time,close[m],time[m],close[m]);
                                       ObjectCreate(chart_id,lvl_sl_line,OBJ_TREND,0,HL_time,lvl_50,time[m],lvl_50);
                                       ObjectCreate(chart_id,lvl_tp_line,OBJ_TREND,0,HL_time,TP1,time[m],TP1);
                                       ObjectCreate(chart_id,lvl_tp2_line,OBJ_TREND,0,HL_time,TP2,time[m],TP2);

                                       ObjectSetInteger(chart_id,entry_line,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_WIDTH,2);

                                       ObjectSetInteger(chart_id,entry_line,OBJPROP_COLOR,clrDarkGreen);
                                       ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_COLOR,clrDarkGreen);
                                       ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_COLOR,clrDarkGreen);
                                       ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_COLOR,clrDarkGreen);

                                       entry_txt = StringFormat("Entry Text%d", m);
                                       lvl_sl_txt = StringFormat("SL Text%d", m);
                                       lvl_tp_txt = StringFormat("TP 1 Text%d", m);
                                       lvl_tp2_txt = StringFormat("TP 2 Text%d", m);

                                       ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0,time[m],lvl_50);
                                       ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL");
                                       ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_COLOR,clrDarkGreen);
                                       ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0,time[m],close[m]);
                                       ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "BUY");
                                       ObjectSetInteger(chart_id,entry_txt,OBJPROP_COLOR,clrDarkGreen);
                                       ObjectSetInteger(chart_id,entry_txt,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0,time[m],TP1);
                                       ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1");
                                       ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_COLOR,clrDarkGreen);
                                       ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0,time[m],TP2);
                                       ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2");
                                       ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_COLOR,clrDarkGreen);
                                       ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_FONTSIZE,15);

                                       buy_object = StringFormat("Buy Object%d", m);
                                       ObjectCreate(chart_id,buy_object,OBJ_ARROW_BUY,0,time[m],close[m]);

                                       break;
                                      }
                                   }


                                }

                              break; // Exit the loop once the pattern is found
                             }
                          }

                        break; // Exit the loop once the higher low is found
                       }
                    }

                  break; // Exit the loop once the higher high is found
                 }
              }
           }
        }
     }
  }
出力

図7:買いシグナル

上の画像では、プレミアムゾーンやディスカウントゾーン、そして買いシグナルのマーカーが正確に描画されていないことが確認できます。これは、チャート上にオブジェクトを描く前に、いくつかの条件が見落とされていたことが原因です。このアプローチを改善するには、買いシグナルが正当なものであることを確認するための追加の検証処理を導入する必要があります。まず、チャートにどのようなオブジェクトを描画する前にも、ローソク足が本当に高値更新(HH)をブレイクしているかどうかを確認する必要があります。HHのブレイクは、強気トレンドの継続を意味しており、買いシグナルが有効であると見なすための重要な条件です。この条件が満たされる前に、エントリーやリスク管理の計算を始めるべきではありません。

次に、HLからプレミアム/ディスカウントボックスの終点までのバーの本数をカウントする必要があります。この工程により、価格変動が許容範囲内に収まっているかを確認でき、市場がどれだけ動いたかを把握する助けとなります。このカウントが完了した後は、HHをブレイクした強気バーの終値が、プレミアム/ディスカウントボックスの近くにあることを確認する必要があります。これにより、買いシグナルが想定された市場構造からかけ離れておらず、妥当な価格帯の中で発生していることが保証されます。


// Declare variables to count bars
int n_bars;     // Number of bars from Higher Low to the end of the Premium/Discount box
int n_bars_2;   // Number of bars from the end of the Premium/Discount box to the bullish bar that broke HH
 if(show_bullish == true) // Check if the bullish trend is to be displayed
     {
      if(rates_total >= bars_check) // Ensure enough bars are available for analysis
        {
         // Loop through the price data starting from a certain point based on bars_check and LookbackBars
         for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++)
           {
            // Check if the current bar is a swing low
            if(IsSwingLow(low, i, LookbackBars))
              {
               // Store the values for the swing low
               L = low[i];
               L_time = time[i];
               L_letter = StringFormat("Low%d", i);

               // Loop through further to find a swing high after the low
               for(int j = i; j < rates_total - LookbackBars; j++)
                 {
                  // Check if the current bar is a swing high and occurs after the identified swing low
                  if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time)
                    {
                     // Store the values for the swing high
                     H = high[j];
                     H_time = time[j];
                     H_letter = StringFormat("High%d", j);

                     // Loop further to find a higher low after the swing high
                     for(int k = j; k < rates_total - LookbackBars; k++)
                       {
                        // Check if the current bar is a swing low and occurs after the swing high
                        if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time)
                          {
                           // Store the values for the higher low
                           HL = low[k];
                           HL_time = time[k];
                           HL_letter = StringFormat("Higher Low%d", j);

                           // Loop further to find a higher high after the higher low
                           for(int l = j ; l < rates_total - LookbackBars; l++)
                             {
                              // Check if the current bar is a swing high and occurs after the higher low
                              if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time)
                                {
                                 // Store the values for the higher high
                                 HH = high[l];
                                 HH_time = time[l];
                                 HH_letter = StringFormat("Higher High%d", l);


                                 // Loop through the bars to check for the conditions for entry
                                 for(int m = l; m < rates_total-1; m++)
                                   {
                                    // Check if the current bar is a bullish bar and if the price has broken the higher high (HH)
                                    if(close[m] > open[m] && close[m] > HH && time[m] >= time[l+LookbackBars])
                                      {
                                       // Count the bars between HL_time and the end of the Premium/Discount box
                                       n_bars = Bars(_Symbol, PERIOD_CURRENT, HL_time, time[l + LookbackBars]);

                                       // Count the bars between the end of the Premium/Discount box and the candle that broke HH
                                       n_bars_2 = Bars(_Symbol, PERIOD_CURRENT, time[l + LookbackBars], time[m]);

                                       // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High
                                       if(L < H && HL < H && HL > L && HH > H && open[l+LookbackBars] <= HH && n_bars_2 < n_bars)
                                         {
                                          // Create and display text objects for Low, High, Higher Low, and Higher High on the chart
                                          ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L);
                                          ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L");
                                          ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15);

                                          ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H);
                                          ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H");
                                          ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15);

                                          ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL);
                                          ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL");
                                          ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15);

                                          ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH);
                                          ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH");
                                          ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15);


                                          // Calculate the 50% retracement level between the Higher Low and Higher High
                                          lvl_50 = HL + ((HH - HL)/2);

                                          // Generate unique names for the premium-discount box and the 50% level line using the current loop index
                                          pre_dis_box = StringFormat("Premium and Discount Box%d", i);
                                          lvl_50_line = StringFormat("Level 50 Line%d", i);

                                          // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High
                                          ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH);

                                          // Create a trend line (horizontal line) marking the 50% retracement level
                                          ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50);

                                          // Set the color of the premium-discount box to dark green
                                          ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen);

                                          // Set the color of the 50% level line to dark green
                                          ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen);

                                          // Set the width of the premium-discount box for better visibility
                                          ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2);

                                          // Set the width of the 50% level line for better visibility
                                          ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2);



                                          // Calculate Take Profit levels based on the 50% retracement
                                          TP1 = close[m] + (close[m] - lvl_50);    // TP1 at 1:1 risk-reward ratio
                                          TP2 = TP1 + (close[m] - lvl_50);          // TP2 at 1:2 risk-reward ratio

                                          // Create unique object names for Entry, Stop Loss, and Take Profit lines and text
                                          entry_line = StringFormat("Entry%d", m);
                                          lvl_sl_line = StringFormat("SL%d", m);
                                          lvl_tp_line =  StringFormat("TP%d", m);
                                          lvl_tp2_line =  StringFormat("TP 2%d", m);

                                          // Create the lines on the chart for Entry, Stop Loss, and Take Profit levels
                                          ObjectCreate(chart_id, entry_line, OBJ_TREND, 0, HL_time, close[m], time[m], close[m]);
                                          ObjectCreate(chart_id, lvl_sl_line, OBJ_TREND, 0, HL_time, lvl_50, time[m], lvl_50);
                                          ObjectCreate(chart_id, lvl_tp_line, OBJ_TREND, 0, HL_time, TP1, time[m], TP1);
                                          ObjectCreate(chart_id, lvl_tp2_line, OBJ_TREND, 0, HL_time, TP2, time[m], TP2);

                                          // Set the properties for the lines (width, color, etc.)
                                          ObjectSetInteger(chart_id, entry_line, OBJPROP_WIDTH, 2);
                                          ObjectSetInteger(chart_id, lvl_sl_line, OBJPROP_WIDTH, 2);
                                          ObjectSetInteger(chart_id, lvl_tp_line, OBJPROP_WIDTH, 2);
                                          ObjectSetInteger(chart_id, lvl_tp2_line, OBJPROP_WIDTH, 2);

                                          ObjectSetInteger(chart_id, entry_line, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, lvl_sl_line, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, lvl_tp_line, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, lvl_tp2_line, OBJPROP_COLOR, clrDarkGreen);

                                          // Create the text labels for Entry, Stop Loss, and Take Profit levels
                                          entry_txt = StringFormat("Entry Text%d", m);
                                          lvl_sl_txt = StringFormat("SL Text%d", m);
                                          lvl_tp_txt = StringFormat("TP 1 Text%d", m);
                                          lvl_tp2_txt = StringFormat("TP 2 Text%d", m);

                                          // Create the text objects for the Entry, Stop Loss, and Take Profit labels
                                          ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0, time[m], lvl_50);
                                          ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL");
                                          ObjectSetInteger(chart_id, lvl_sl_txt, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, lvl_sl_txt, OBJPROP_FONTSIZE, 15);

                                          ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0, time[m], close[m]);
                                          ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "BUY");
                                          ObjectSetInteger(chart_id, entry_txt, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, entry_txt, OBJPROP_FONTSIZE, 15);

                                          ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0, time[m], TP1);
                                          ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1");
                                          ObjectSetInteger(chart_id, lvl_tp_txt, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, lvl_tp_txt, OBJPROP_FONTSIZE, 15);

                                          ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0, time[m], TP2);
                                          ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2");
                                          ObjectSetInteger(chart_id, lvl_tp2_txt, OBJPROP_COLOR, clrDarkGreen);
                                          ObjectSetInteger(chart_id, lvl_tp2_txt, OBJPROP_FONTSIZE, 15);

                                          // Create a Buy arrow object to indicate the Buy signal on the chart
                                          buy_object = StringFormat("Buy Object%d", m);
                                          ObjectCreate(chart_id, buy_object, OBJ_ARROW_BUY, 0, time[m], close[m]);

                                          break; // Exit the loop once a Buy signal is found
                                         }
                                      }

                                   }

                                 break; // Exit the loop once the pattern is found
                                }
                             }

                           break; // Exit the loop once the higher low is found
                          }
                       }

                     break; // Exit the loop once the higher high is found
                    }
                 }
              }
           }
        }
     }

出力

図8:買いシグナル

説明

コードのグローバル領域では、n_barsとn_bars_2という2つの整数型変数が宣言されています。これらの変数は、強気の市場構造パターンにおける重要なポイント間に存在するローソク足(バー)の本数を判定するために使用されます。具体的には、n_barsはプレミアム/ディスカウントボックスの終点(time[l + LookbackBars])からHLまでのバー数を示します。一方で、n_bars_2はHHをブレイクした強気のローソク足と、プレミアム/ディスカウントボックスの終点との間のバー数をカウントします。このカウントにより、価格の動きが理想的なトレードゾーンからどれだけ離れているか、または買いシグナルがまだ有効かどうかを判断するための基準が得られます。

これらの変数は、コードの後半で強気の市場構造をより厳密に評価する追加の検証条件として使用されます。L、H、HL、HHを特定したあと(それぞれが以下の構造に従っていることを確認:L < H, HL < H, HL > L, HH > H)、さらに条件「プレミアム/ディスカウントボックスを終えたローソク足の始値がHHを超えていないこと」、そして「n_bars_2 < n_barsであること」が確認されます。この条件により、HHをブレイクする強気のローソク足が、パターンの形成からあまり時間をおかずに出現していることが保証されます。もし時間的に離れすぎている場合、そのブレイクアウトは信頼性の低い、あるいは弱いシグナルである可能性があるため、排除されます。

以前に使用していたObjectCreateやObjectSet*といったチャート描画用の関数群(チャートのL、H、HL、HH、プレミアム/ディスカウントボックス、50%ライン、およびエントリー/SL/TPマーカーを描画する処理)は、このより厳密な検証を実施するために、このif文の内部に移動されました。つまり、すべての構造的条件と時間的条件が満たされたときにのみ、これらの視覚要素がチャート上に表示されるようになっています。この仕組みによって、チャートが誤ったシグナルや時期尚早な描画で乱雑になるのを防ぎ、明確で信頼性の高い視覚表示を維持できるようになっています。

2.3. 弱気トレンド

このインジケーターが売りシグナルを示す前に、市場構造を使って下降トレンドを確認する必要があります。これは、H、L、LH、LLという一連の重要な価格ポイントを見つけることでおこなわれます。このパターンは、売り手が市場を支配しており、価格がさらに下落する可能性が高いことを示しており、弱気のモメンタムを裏付けるものです。インジケーターはこの構造が確認され次第、有効な売りシグナルの検出を開始します。

弱気トレンド

// Variables for Bearish Market Structure
double LH;              // Lower High: the high formed after the initial low in a downtrend
datetime LH_time;       // Time of the Lower High
string LH_letter;       // Label used to display the Lower High on the chart (e.g., "LH")
double LL;              // Lower Low: the new low formed after the Lower High in a downtrend
datetime LL_time;       // Time of the Lower Low
string LL_letter;       // Label used to display the Lower Low on the chart (e.g., "LL")
string sell_object; // Arrow object to indicate the Sell signal on the chart
// BEARISH TREND
if(show_bearish == true)  // Check if the user enabled the bearish trend display
  {
   if(rates_total >= bars_check)  // Ensure enough candles are available for processing
     {
      // Loop through historical bars to find a swing high (potential start of bearish structure)
      for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++)
        {
         if(IsSwingHigh(high, i, LookbackBars))  // Detect first swing high
           {
            H = high[i];
            H_time = time[i];
            H_letter = StringFormat("High B%d", i);  // Label for the high

            // From the swing high, look for the next swing low
            for(int j = i; j < rates_total - LookbackBars; j++)
              {
               if(IsSwingLow(low, j, LookbackBars) && time[j] > H_time)  // Confirm next swing low
                 {
                  L = low[j];
                  L_time = time[j];
                  L_letter = StringFormat("Low B%d", j);  // Label for the low

                  // From the swing low, look for the Lower High
                  for(int k = j; k < rates_total - LookbackBars; k++)
                    {
                     if(IsSwingHigh(high, k, LookbackBars) && time[k] > L_time)
                       {
                        LH = high[k];
                        LH_time = time[k];
                        LH_letter = StringFormat("Lower High%d", k);  // Label for the Lower High

                        // From the LH, find a Lower Low
                        for(int l = j ; l < rates_total - LookbackBars; l++)
                          {
                           if(IsSwingLow(low, l, LookbackBars) && time[l] > LH_time)
                             {
                              LL = low[l];
                              LL_time = time[l];
                              LL_letter = StringFormat("Lower Low%d", l);  // Label for Lower Low

                              // Calculate 50% retracement level from LH to LL
                              lvl_50 = LL + ((LH - LL)/2);

                              // Prepare object names
                              pre_dis_box = StringFormat("Gan Box B%d", i);
                              lvl_50_line = StringFormat("Level 50 Line B%d", i);

                              // Search for a bearish entry condition
                              for(int m = l; m < rates_total-1; m++)
                                {
                                 // Confirm bearish candle breaking below the LL
                                 if(close[m] < open[m] && close[m] < LL && time[m] >= time[l+LookbackBars])
                                   {
                                    // Count bars for pattern distance validation
                                    n_bars = Bars(_Symbol,PERIOD_CURRENT,LH_time, time[l+LookbackBars]);  // From LH to box end
                                    n_bars_2 = Bars(_Symbol,PERIOD_CURRENT,time[l+LookbackBars], time[m]);  // From box end to break candle

                                    // Confirm valid bearish structure and proximity of break candle
                                    if(H > L && LH > L && LH < H && LL < L && open[l+LookbackBars] >= LL && n_bars_2 < n_bars)
                                      {
                                       // Draw the Premium/Discount box
                                       ObjectCreate(chart_id,pre_dis_box, OBJ_RECTANGLE,0,LH_time,LH, time[l+LookbackBars],LL);
                                       ObjectCreate(chart_id,lvl_50_line, OBJ_TREND,0,LH_time,lvl_50, time[l+LookbackBars],lvl_50);

                                       ObjectSetInteger(chart_id,pre_dis_box,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,lvl_50_line,OBJPROP_WIDTH,2);

                                       // Label the structure points
                                       ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H);
                                       ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H");
                                       ObjectSetInteger(chart_id,H_letter,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L);
                                       ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L");
                                       ObjectSetInteger(chart_id,L_letter,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, LH_letter, OBJ_TEXT, 0, LH_time, LH);
                                       ObjectSetString(chart_id, LH_letter, OBJPROP_TEXT, "LH");
                                       ObjectSetInteger(chart_id,LH_letter,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, LL_letter, OBJ_TEXT, 0, LL_time, LL);
                                       ObjectSetString(chart_id, LL_letter, OBJPROP_TEXT, "LL");
                                       ObjectSetInteger(chart_id,LL_letter,OBJPROP_FONTSIZE,15);

                                       ObjectSetInteger(chart_id,H_letter,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,L_letter,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,LL_letter,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,LH_letter,OBJPROP_WIDTH,2);

                                       // Calculate Take Profits based on 1:1 and 1:2 RR
                                       TP1 = close[m] - (lvl_50 - close[m]);
                                       TP2 = TP1 - (lvl_50 - close[m]);

                                       // Generate entry, SL and TP object names
                                       entry_line = StringFormat("Entry B%d", m);
                                       lvl_sl_line = StringFormat("SL B%d", m);
                                       lvl_tp_line =  StringFormat("TP B%d", m);
                                       lvl_tp2_line =  StringFormat("TP 2 B%d", m);

                                       // Draw entry, SL, TP1, TP2 levels
                                       ObjectCreate(chart_id,entry_line,OBJ_TREND,0,LH_time,close[m],time[m],close[m]);
                                       ObjectCreate(chart_id,lvl_sl_line, OBJ_TREND,0,LH_time,lvl_50, time[m],lvl_50);
                                       ObjectCreate(chart_id,lvl_tp_line, OBJ_TREND,0,LH_time,TP1, time[m],TP1);
                                       ObjectCreate(chart_id,lvl_tp2_line, OBJ_TREND,0,LH_time,TP2, time[m],TP2);

                                       ObjectSetInteger(chart_id,entry_line,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_WIDTH,2);
                                       ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_WIDTH,2);

                                       // Generate text labels
                                       entry_txt = StringFormat("Entry Text B%d", m);
                                       lvl_sl_txt = StringFormat("SL Text B%d", m);
                                       lvl_tp_txt = StringFormat("TP Text B%d", m);
                                       lvl_tp2_txt = StringFormat("TP 2 Text B%d", m);

                                       ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0,time[m],close[m]);
                                       ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "SELL");
                                       ObjectSetInteger(chart_id,entry_txt,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0,time[m],lvl_50);
                                       ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL");
                                       ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0,time[m],TP1);
                                       ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1");
                                       ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_FONTSIZE,15);

                                       ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0,time[m],TP2);
                                       ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2");
                                       ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_FONTSIZE,15);

                                       // Draw sell arrow
                                       sell_object = StringFormat("Sell Object%d", m);
                                       ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,0,time[m],close[m]);
                                      }

                                    break;  // Exit loop after valid setup
                                   }
                                }

                              break;  // Exit LL search
                             }
                          }

                        break;  // Exit LH search
                       }
                    }

                  break;  // Exit L search
                 }
              }
           }
        }
     }
  }

出力

図10:売りシグナル

説明

コードはまず、「if(show_bearish == true)」を使って、ユーザーが下降トレンドロジックを有効にしているかどうかを確認することから始まります。このオプションが有効で、かつバーの本数が十分ある(rates_total >= bars_check)場合、インジケーターは過去のローソク足をループ処理しながら、正当な下降の市場構造を探します。このプロセスの最初のステップは、スイングハイ(H)を特定することです。スイングハイが特定された後、その次に現れるスイングロー(L)を探します。それが見つかった場合、コードはさらに、LLを探し、それによって下降トレンド構造を確定します。続いて、最初の高値Hよりも低い位置にあるスイングハイ(LH)を探します。こうして見つけたH、L、LH、LLの値とそのタイムスタンプを用いて、チャート上に「H」、「L」、「LH」、「LL」といったラベルが表示されます。

次に、プレミアム/ディスカウントゾーンのボックスがLHからLLに向けて描画され、50%リトレースメントレベルが次の式「lvl_50 = LL + ((LH - LL)/2」により算出されます。その後、インジケーターは、LLを下抜けてクローズする陰線(終値が始値より低い)を探します。これは、取引関連のオブジェクト(エントリーポイント、ストップロス、TP1、TP2)を配置する前の確認条件です。また、n_barsおよびn_bars_2の2つの変数を使用して、構造のブレイクが適切な本数のバーの範囲内で起きているかも検証します。n_barsはLHからボックス終点までのバー数をカウントし、n_bars_2はボックス終点からLLをブレイクした陰線までのバー数をカウントします。構造が正しく、ブレイクが有効であり、距離も適切であるというすべての条件が満たされた場合にのみ、ローソク足の終値にエントリーラインが描画され、ストップロス(SL)は50%レベルに設定されます。さらに、テイクプロフィット1(TP1)とテイクプロフィット2(TP2)は、それぞれ1:1および1:2のリスクリワード比に基づいて配置されます。

加えて、下降を示す矢印と「SELL」というテキストが、該当する陰線の上に表示されます。この下降トレンドに関するロジックは、基本的には上昇トレンドパターンのロジックと同じ構造を持ちますが、その内容が反転しているだけです。すでに上昇トレンドについては詳細に説明されているため、ここでの解説は簡潔にとどめられています。市場構造や判断の根拠は同じですが、価格が上昇するのではなく下降する方向に向かっている点が異なります。


結論

本記事では、安値(L)、安値更新(LL)、高値引き下げ(HL)、高値(H)、高値更新(HH)といった主要なポイントを検出することで市場構造を識別する、MQL5のカスタムインジケーターを構築しました。これらのポイントをもとに、インジケーターは上昇トレンドまたは下降トレンドを判断し、構造に基づいたパターンに従って、エントリーポイント、50%レベルに設定されたストップロス、テイクプロフィット(TP1およびTP2)を自動的に描画します。また、プレミアムゾーンとディスカウントゾーンをチャート上に可視化することで、価格が反応しやすい領域を視覚的に強調します。すべてのチャートオブジェクトは、特定の条件が満たされたときにのみ描画されるため、シグナルは明確で信頼性の高いものに保たれています。 


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

添付されたファイル |
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
ペア取引における平均回帰による統計的裁定取引:数学で市場を攻略する ペア取引における平均回帰による統計的裁定取引:数学で市場を攻略する
本記事では、ポートフォリオレベルの統計的アービトラージの基本的な概念を紹介します。数学の深い知識がない読者にも理解しやすく説明し、実際の運用を始めるためのコンセプトフレームワークを提案することを目的としています。記事には、動作するエキスパートアドバイザー(EA)と、1年間のバックテストに関する注記、再現用の設定ファイル(.iniファイル)も含まれています。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
ログレコードをマスターする(第6回):ログをデータベースに保存する ログレコードをマスターする(第6回):ログをデータベースに保存する
この記事では、ログを構造化され、スケーラブルな方法で保存するためにデータベースを使用する手法を取り上げます。基本的な概念、主要な操作、MQL5におけるデータベースハンドラの設定と実装を順を追って解説し、最後にその結果を検証し、このアプローチが最適化と効率的なモニタリングにどのように役立つかを明らかにします。