English Deutsch
preview
MQL5でのもみ合いレンジブレイクアウト戦略に基づくエキスパートアドバイザー(EA)の開発

MQL5でのもみ合いレンジブレイクアウト戦略に基づくエキスパートアドバイザー(EA)の開発

MetaTrader 5トレーディング | 6 9月 2024, 09:58
35 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

この記事では、MetaQuotes Language 5 (MQL5)を使用して、MetaTrader 5 (MT5)向けのもみ合いレンジブレイクアウト(Consolidation Range Breakout)戦略に基づくEAの開発を解説します。 目まぐるしく変化する金融取引の世界では、市場パターンや行動を活用した戦略が成功の鍵となります。その中でも、もみ合い期間を特定し、その後のブレイクアウトを取引する「もみ合いレンジブレイクアウト戦略」は、特に効果的です。この戦略は、ボラティリティの低い期間に続く大きな値動きを捉えるために有効です。 以下のトピックを通じて、この戦略に基づくEAの作成方法を探ります。

  1. 戦略の概要
  2. 戦略の青写真
  3. MetaQuotes言語5(MQL5)による実装
  4. バックテスト結果
  5. 結論

この記事を最後まで読むことで、MQL5を使ったもみ合いレンジブレイクアウト戦略に基づく堅牢なEAの開発方法を総合的に理解し、ご自身の取引ツールキットを強化する知識を得ることができるでしょう。ここでは、基となる統合開発環境 (IDE)コーディング環境としてMetaQuotes Language 5 (MQL5)を広範囲に使用し、MetaTrader 5 (MT5)取引端末でファイルを実行します。従って、上記のバージョンを持っていることが最も重要になります。それでは、さっそく始めましょう。


戦略の概要

もみ合いレンジブレイクアウト戦略を理解するために、これをいくつかの要素に分けて説明します。

  • もみ合いレンジの説明

もみ合いレンジとは、価格が強い上昇や下降を示さず、特定のレンジ内で横ばいに変動する期間のことです。この期間はボラティリティが低く、価格は明確な支持レベル (下限) と抵抗レベル (上限) の間で推移します。トレーダーはこの局面を利用して、ブレイクアウトの可能性があるポイントを予測します。

  • 戦略の仕組み

もみ合いレンジブレイクアウト戦略は、もみ合い期間中の価格の予測可能な動きを活用し、ブレイクアウトを識別して取引します。仕組みはこうです。

もみ合いレンジを特定する:まず、直近の値動きを分析してもみ合いレンジを検出します。これは、特定の数のバー(ローソク足)の最高値と最安値を特定し、レンジの上限と下限を定義するプロセスです。これらは通常、レンジ内で抵抗線と支持線として機能します。時間枠の選択は固定されておらず、どのチャートでも検出プロセスをおこなうことができるため、取引スタイルに応じたチャート時間枠を選択する必要があります。

ブレイクアウトを監視する:もみ合いレンジが設定されたら、その上限や下限を突破する価格の動きを監視します。ブレイクアウトは、価格が抵抗レベルを上回ったり、支持レベルを下回ったりした際に発生します。他のトレーダーは、レンジをブレイクしたのと同じローソク足をスキャルピングと見なします。つまり、価格が極端なレンジを下回ったり上回ったりしたら、すでにブレイクアウトしたと見なすのです。

ブレイクアウトを取引するブレイクアウトが検出されると、戦略はブレイクアウトの方向に従って取引を開始します。抵抗線を上抜けた場合は買い注文が、支持線を割り込んだ場合は売り注文が出されます。なお、リトレースメントを待つトレーダーもいます。つまり、ブレイクアウト後にさらなる確認のために価格がレンジを再訪するのを待ち、再度同じ方向にブレイクアウトした場合に市場にエントリするという方法です。ただし、ここではリトレースメントについては考慮しません。 

  • 戦略の実施

もみ合いレンジブレイクアウト戦略を実行するためには、次のステップを踏みます。

レンジパラメータを定義する:分析対象とするローソク足の数を決定し、ブレイクアウトの基準を設定します。バーの範囲と価格の目標範囲が決定され、設定されます。例えば、700ポイント(70 pips)の範囲内で少なくとも10本のローソク足が必要とされることがあります。

検出ロジックを開発する:過去の価格データをスキャンし、指定された範囲内の最高値と最安値を特定するコードを作成します。コードは、もみ合いレンジが有効であるとみなされるために満たされなければならない条件を明確にすべきであり、仮定は曖昧さを避けるために明確に概説されなければなりません。

リアルタイムの価格データを監視する:入ってくる価格データを継続的に監視し、ブレイクアウトを即座に検出します。ティックごとに監視をおこない、必要なければ、新しいローソク足が生成されるたびに監視をおこなう必要があります。

取引を実行する:ブレイクアウトが発生した際に適切なストップロスやテイクプロフィットを設定し、取引を実行するための取引実行ロジックを実装します。

最適化とテスト:本番取引に導入する前に、過去データでバックテストを行い、パラメータを最適化し、戦略の有効性を確認します。これにより、最適なパラメータを特定し、システムを強化改善するために改善やフィルターが必要な主要機能をピンポイントで特定することができます。

これらのステップを踏むことで、もみ合いレンジブレイクアウト戦略を活用した強力なツールを作成できます。戦略の実装に必要なパラメータと一般的な要件は、以下の通りです。

もみ合いブレイクアウト図解


戦略の青写真

この概念を簡単に理解するために、設計図で可視化してみましょう。

  • もみ合いレンジの上方ブレイクアウト:

上方ブレイクアウト

  • もみ合いレンジの下方ブレイクアウト:

下方ブレイクアウト


MetaQuotes言語5(MQL5)による実装

もみ合いレンジブレイクアウト戦略の基本的なステップとアプローチを理解したら、その理論を自動化し、MetaQuotes Language 5 (MQL5)を使用してMetaTrader 5 (MT5)向けのEAを作成してみましょう。

EAを作成するには、MetaTrader 5端末で[ツール]タブをクリックし、[MetaQuotes言語エディタ]を選択するか、キーボードのF4を押します。または、ツールバーのIDE (Integrated Development Environment)アイコンをクリックします。これにより、自動売買ロボット、テクニカル指標、スクリプト、関数のライブラリを作成できるMetaQuotes言語エディタ環境が開きます。

METAEDITORを開く

MetaEditorを開いたら、ツールバーの[ファイル]タブで[新しいファイル]を選択するか、CTRL + Nキーを押して新規ドキュメントを開きます。または、[ツール]タブの新規アイコンをクリックすることもできます。MQLウィザードのポップアップが表示されます。

NEW EA

ウィザードが表示されたら、[EA(テンプレート)]を選択し、[次へ]をクリックします。

MQL WIZARD

EAの一般プロパティで、[名前]フィールドにEAのファイル名を入力します。フォルダが存在しない場合にフォルダを指定または作成するには、EA名の前にバックスラッシュを使用することに注意してください。例えば、ここではデフォルトで「Experts」となっています。つまり、私たちのEAはExpertsフォルダに作成され、そこで見つけることができます。他のフィールドはごく簡単ですが、ウィザードの一番下にあるリンクから、正確な手順を知ることができます。

EA名

希望する EAファイル名を入力した後、[次へ]をクリックし、[完了]をクリックします。ここまでくれば、あとはコードを書いて戦略をプログラムするだけです。

まず、ソースコードの先頭に#includeを使用して取引インスタンスをインクルードします。これでCTradeクラスにアクセスできるようになります。これを使用して取引オブジェクトを作成します。これは取引を開始するために必要なので、非常に重要です。

#include <Trade/Trade.mqh> // Include the trade library
CTrade obj_Trade; // Create an instance of the CTrade class

プリプロセッサは#include<Trade/Trade.mqh>という行をTrade.mqhというファイルの内容に置き換えます。角括弧は、Trade.mqhファイルが標準ディレクトリ(通常、terminal_installation_directory\MQL5\Include)から取得されることを示します。カレントディレクトリは検索に含まれません。この行はプログラム中のどこにでも配置できますが、通常は、より良いコード構造と参照を容易にするために、すべてのインクルージョンはソースコードの先頭に置かれます。CTradeクラスのobj_Tradeオブジェクトを宣言すると、MQL5開発者のおかげで、そのクラスに含まれるメソッドに簡単にアクセスできるようになります。

CTRADEクラス

チャートにレンジをグラフィカルに描く必要があるため、そのレンジに名前を付ける必要があります。同じ矩形レンジオブジェクトを使用し、描画後に再描画することなく設定を更新するため、1つのオブジェクトを可視化に利用します。これにより、静的な名前を定義し、簡単に呼び出して即座に再利用できるようにします。具体的には、次のように定義します。

#define rangeNAME "CONSOLIDATION RANGE" // Define the name of the consolidation range

#defineキーワードを使用して、「rangeNAME」という名前のマクロを値「CONSOLIDATION RANGE」で定義し、もみ合いレンジ名を簡単に保存します。これにより、レベルを作成するたびに名前を繰り返し再入力する必要がなくなり、大幅に時間を節約し、名前を間違って指定する可能性を減らすことができます。基本的に、マクロはコンパイル時のテキスト置換に使用されます。

ここでも、プロットされる矩形の座標を保存する必要があります。これらは2次元(2D)座標(x,y)であり、それぞれx1,y1、x2,y2として文書化された1番目と2番目の位置を明示的に特定するために使用されます。価格チャートでは、X軸は日付と時間の目盛りで、Y軸は価格の目盛りで表されます。理解しやすく、参照しやすくするために、これらを視覚的に図解してみましょう。

座標表現

この画像から、なぜ矩形オブジェクトのプロットに座標が必要なのかは明らです。以下は、座標を更新するたびに宣言することなく、確実に範囲座標を保存するために使用されるロジックです。

datetime TIME1_X1, TIME2_Y2; // Declare datetime variables to hold range start and end times
double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices

ここでは、datetimeデータ型の変数を2つ宣言しています。この方法は、同じデータ型の複数の変数を一度に宣言する「シングルデータ型宣言」と呼ばれます。これは、同じ型の複数の変数を1つの文で宣言する省略記法で、コードの行数を減らし、関連する変数をひとまとめにして簡潔さと一貫性を保ちつつ、それらが同じタイプであり、同じような目的で使用される可能性があることを理解しやすくします。次のように書くこともできます。

datetime TIME1_X1;
datetime TIME2_Y2;

変数TIME1_X1はX軸に沿った第一座標の時間値を保持し、TIME2_Y2はやはりX軸に沿った第二座標の時間値を保持します。同様に、価格座標を次のように宣言します。

double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices

ボラティリティの低さによって引き起こされるもみ合いレンジの確立を評価するためには、新しいバーごとに市場を常にスキャンする必要があります。そのため、レンジが存在するかどうかを示すフラグと、価格がレンジ内にあるかどうかを示すフラグを格納するために、2つの変数が必要になります。

bool isRangeExist = false; // Flag to check if the range exists
bool isInRange = false; // Flag to check if we are currently within the range

ここでは、isRangeExistとisInRangeという2つのブール変数を定義し、初期化します。 isRangeExist変数は、もみ合いレンジが特定され、チャート上にプロットされたかどうかを示すフラグとして機能します。開始時にはレンジが確立されていないため、falseに初期化します。一方、isInRange変数もfalseに初期化され、現在の市場価格が特定されたもみ合いレンジ内にあるかどうかを判断するために使用されます。これらのフラグは、レンジ検出とブレイクアウト監視プロセスの状態を管理し、適切な条件が満たされた場合にのみアクションが実行されるようにするため、EAのロジックにとって非常に重要です。

グローバルスコープでは、考慮するローソク足の最小範囲数とポイント単位の範囲サイズを定義する必要があります。これは、理論的な部分で述べたように、これらのパラメータがもみ合いレンジの妥当性を維持し、意味のあるもみ合いレンジを確保するために極めて重要であるためです。 

int rangeBars = 10; // Number of bars to consider for the range
int rangeSizePoints = 400; // Maximum range size in points

ここでも2つの整数変数rangeBarsとrangeSizePointsを宣言し、初期化します。変数rangeBarsを10に設定し、もみ合いレンジを決定するために分析するバー(またはローソク足)の数を指定します。つまり、過去10本のバーを振り返って最高値と最安値を見つけ、レンジを定義します。一方、変数rangeSizePointsは400に設定し、もみ合いレンジの最大許容サイズをポイントで定義します。この10本のバーの最高値と最安値の幅が400ポイントを超える場合は、有効なもみ合いレンジとはみなされません。これらのパラメータは、レンジの基準を設定し、価格データから意味のあるもみ合い期間を確実に特定するために不可欠です。 

最後に、ポジションを建てるので、ストップロスとテイクプロフィットのポイントを定義します。 

double sl_points = 500.0; // Stop loss points
double tp_points = 500.0; // Take profit points

グローバルスコープで必要なのはこれだけです。グローバルスコープとは何でしょうか。グローバルスコープとは、関数やブロックの外側で、変数や関数、その他の要素にコード全体を通してアクセスできるプログラムの領域を指します。変数や関数がグローバルスコープで宣言されると、プログラムのどの部分からもアクセスしたり変更したりできるようになります。 

すべてのアクティビティはOnTickイベントハンドラで実行されます。これは純粋なプライスアクションであり、このイベントハンドラに大きく依存します。したがって、このコードの中心となる関数が受け取るパラメータを見てみましょう。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   
}

すでに見たように、これは引数をとらず、何も返さないシンプルながら重要な関数です。これはvoid関数です。つまり、何も返す必要がありません。この関数はEAで使用され、新しいティック、つまり特定の商品の価格相場が変化したときに実行されます。

OnTick関数は価格相場の変化ごとに呼び出されるため、少なくとも不必要なコードの実行を避け、デバイスのメモリを節約するために、ティックごとではなくバーごとに1回コードを実行できる制御ロジックを定義する必要があります。これは、もみ合いレンジのセットアップを探す際に重要です。各ティックでセットアップを探す必要はなく、同じローソク足であれば常に同じ結果が得られるためです。ロジックは次の通りです。

   int currBars = iBars(_Symbol,_Period); // Get the current number of bars
   static int prevBars = currBars; // Static variable to store the previous number of bars
   static bool isNewBar = false; // Static flag to check if a new bar has appeared
   if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed
   else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars

まず、指定された取引銘柄と期間(または時間枠)のチャート上の現在のバーの数を格納する整数変数currBarsを宣言します。これは、iBars関数を使用することで実現できます。iBars関数は2つの引数、つまり銘柄と期間だけを取ります。次に、static整数変数prevBarsを宣言し、これを現在のバー数で初期化します。staticキーワードは、prevBars変数が関数呼び出しの間、その値を保持し、前回のティックからのバー数を記憶することを保証します。さらに、staticブーリアン変数isNewBarを宣言し、これをfalseに初期化します。この変数は、新しいバーが現れたかどうかを追跡するために使用されます。次に、条件文を用いて現在のバー数と前回のバー数が等しいかどうかを確認します。もし両者が等しければ、新しいバーが形成されていないことを意味し、新しいバー生成のフラグをfalseに設定します。それ以外の場合、前のバーと現在のバーが異なる場合は、バーの数が増えたことを意味し、新しいバーが出現したことを示します。したがって、新しいバーの生成フラグをtrueに設定し、以前のバーの値を現在のバーに更新します。

さて、各バーが生成され、もみ合いレンジがない場合、低ボラティリティ期間の可能性があるかどうか、事前に定義されたバーをスキャンする必要があります。 

   if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared

      ...

   }

isRangeExistがfalseであれば、まだレンジが確立されていないことを意味し、isNewBarがtrueであれば、新しいバーが出現したことを意味します。これは、もみ合いレンジがまだ特定されておらず、新しいバーが形成された場合にのみ、処理を進めることを保証します。

チャート上にプロットされる矩形オブジェクトの最初のポイントの座標を決定するには、極値抵抗レベルが必要です。これは、事前に定義したバースキャン範囲の最後のバーの時間と、範囲内で最も高いバーの価格です。

      TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range
      int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range
      PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range

まず、指定された銘柄と期間の特定のバーの開始時刻を返すiTime関数を使用して、レンジの開始時刻を設定します。この関数は3つの入力パラメータまたは引数を取ります。_Symbolは取引銘柄(例:AUDUSD)、_Periodは時間枠(例:1分足の場合はPERIOD_M1)、そしてrangeBarsは指定した期間数前のバーのインデックスです。結果は「TIME1_X1」に格納され、もみ合いレンジの開始時刻を示します。

次に、指定された数のバーの中で最も高い高値を持つバーのインデックスを返すiHighest関数を使用して、指定された範囲内で最も高い高値を持つバーを検索します。この関数は5つの引数を取ります。最初の2つのパラメータが何をするものかは、すでに説明したので、改めて説明する必要はありません。3番目のパラメータはMODE_HIGHで、最高値を探していることを示します。4番目のrangeBarsは、スキャン分析で考慮するバーの数を指定し、最後の1は、現在形成されているバーの前のバーから探し始めることを意味します。厳密にいえば、現在形成中のバーはインデックス0であり、その前のバーはインデックス1です。結果のインデックスは、整数変数highestHigh_BarIndexに格納されます。

最後に、特定のバーの高値を返すiHigh関数を使って、そのバーの最高値を取得します。この関数は3つの入力パラメータを取ります。最初の2つのパラメータは特に簡単ですが、3番目の引数であるhesthomHigh_BarIndexは、前のステップで決定されたバーのインデックスです。高値はPRICE1_Y1に格納されます。これらの変数によって、もみ合いレンジの始点と最高点を定義でき、レンジをプロットし、後にブレイクアウトを検出するために重要です。

2つ目の座標を求めるには、1つ目の点の座標を求めたときと同様のアプローチを用います。 

      TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time
      int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range
      PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range

コードの違いは、まず時間がインデックス0にある現在のバーにリンクされている点です。次に、あらかじめ定義されたバー範囲内で最も低いバーのインデックスを取得するために、iLowest 関数を使用し、「MODE_LOW」を指定して最安値を探していることを示します。最後に、iLow関数を使用して、最も低いバーの価格を取得します。一言で言えば、任意のグラフをとった場合に必要な座標を可視化したものです。

座標ビジュアライゼーション

もみ合いレンジポイントを取得した後は、その有効性を確認する必要があります。導入部分で前述した条件を満たしているかどうかを確認し、有効なレンジの条件が整っていることを確かめてから、もみ合いレンジポイントを検討し、チャートにプロットする必要があります。

      isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points

レンジ内の最高値(PRICE1_Y1)と最安値(PRICE2_Y2)の差を計算し、その差を1ポイントの大きさ_Pointで割ってポイントに変換します。例えば、最高値が0.66777、最安値が0.66773の場合、数学的な差は0.66777 - 0.66773 = 0.00004です。想定される銘柄のポイント値が0.00001であれば、この結果をポイントで割ると、0.00004/0.00001 = 4ポイントとなります。この値は、ポイント単位で定義された最大許容範囲サイズであるrangeSizePointsと比較されます。

最後に、特定された有効なレンジがあるかどうかを確認し、有効であればチャートにプロットし、作成に成功したことを知らせます。

      if (isInRange){ // If the range size is valid
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range
         isRangeExist = true; // Set the range exist flag to true
         Print("RANGE PLOTTED"); // Print a message indicating the range is plotted
      }

ここでは、isInRange変数を評価して、特定されたもみ合いレンジが有効かどうかを確認します。変数フラグがtrueで、レンジのサイズが許容範囲内であることを示せば、もみ合いレンジをチャートにプロットします。プロットのために、入力パラメータrangeNAME、TIME1_X1、PRICE1_Y1、TIME2_Y2、PRICE2_Y2を使用してplotConsolidationRange関数を呼び出します。レンジのプロットが成功した場合は、isRangeExistフラグをtrueにセットして、有効なレンジが特定されてプロットされたことを示します。さらに、ロギングのために端末にRANGE PLOTTEDというメッセージを表示し、もみ合いレンジが正常に可視化されたことを確認します。

もみ合いレンジのプロットまたは更新をおこなう関数のコードスニペットは以下の通りです。

//+------------------------------------------------------------------+
//| Function to plot the consolidation range                         |
//| rangeName - name of the range object                             |
//| time1_x1 - start time of the range                               |
//| price1_y1 - high price of the range                              |
//| time2_x2 - end time of the range                                 |
//| price2_y2 - low price of the range                               |
//+------------------------------------------------------------------+
void plotConsolidationRange(string rangeName,datetime time1_x1,double price1_y1,
   datetime time2_x2,double price2_y2){
   if (ObjectFind(0,rangeName) < 0){ // If the range object does not exist
      ObjectCreate(0,rangeName,OBJ_RECTANGLE,0,time1_x1,price1_y1,time2_x2,price2_y2); // Create the range object
      ObjectSetInteger(0,rangeName,OBJPROP_COLOR,clrBlue); // Set the color of the range
      ObjectSetInteger(0,rangeName,OBJPROP_FILL,true); // Enable fill for the range
      ObjectSetInteger(0,rangeName,OBJPROP_WIDTH,5); // Set the width of the range
   }
   else { // If the range object exists
      ObjectSetInteger(0,rangeName,OBJPROP_TIME,0,time1_x1); // Update the start time of the range
      ObjectSetDouble(0,rangeName,OBJPROP_PRICE,0,price1_y1); // Update the high price of the range
      ObjectSetInteger(0,rangeName,OBJPROP_TIME,1,time2_x2); // Update the end time of the range
      ObjectSetDouble(0,rangeName,OBJPROP_PRICE,1,price2_y2); // Update the low price of the range
   }
   ChartRedraw(0); // Redraw the chart to reflect changes
}

まず、plotConsolidationRangeというvoid 関数を定義し、5つのパラメータ(引数)を渡します。これらのパラメータは、範囲名、最初のポイントの2つの座標、2番目のポイントの2つの座標です。関数内では、まずObjectFind関数を使って、指定されたオブジェクトが存在するかどうかを確認します。オブジェクトが見つからない場合は負の整数が返されます。条件文を使用して、オブジェクトが存在する場合には、OBJ_RECTANGLEとして特定されたオブジェクトを現在の時刻と指定された価格に合わせて、1番目と2番目の座標に基づいて作成します。次に、オブジェクトの色、領域の塗りつぶし、幅を設定します。もしオブジェクトが見つかれば、その時間と価格を指定された値に更新し、変更をチャートに適用するために再描画します。修飾値0は第一座標を、1は第二座標を指します。

これだけで、特定したもみ合いレンジをチャートにプロットすることができます。そのソースコードの全文は以下の通りです。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   
   int currBars = iBars(_Symbol,_Period); // Get the current number of bars
   static int prevBars = currBars; // Static variable to store the previous number of bars
   static bool isNewBar = false; // Static flag to check if a new bar has appeared
   if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed
   else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars
   
   if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared
      TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range
      int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range
      PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range
      
      TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time
      int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range
      PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range
      
      isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points
      
      if (isInRange){ // If the range size is valid
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range
         isRangeExist = true; // Set the range exist flag to true
         Print("RANGE PLOTTED"); // Print a message indicating the range is plotted
      }
   }

}

結果をまとめると、以下のようになります。

プロットされた静的塗りつぶし範囲

事前に定義されたポイント内にレンジをプロットし、操作ログにプロットインスタンスの情報を表示していることがわかります。レンジを塗りつぶす必要がない場合は、fillプロパティのフラグをfalseに設定します。これにより、矩形のラインプロパティが描画され、幅が有効になり、定義どおりに適用されます。以下はそのロジックです。

      ObjectSetInteger(0,rangeName,OBJPROP_FILL,false); // Disable fill for the range

これらの結果、以下のような範囲になります。

未塗りつぶしレンジ

この記事では、塗りつぶしレンジを使用することにします。レンジが正しく設定できることを確認したので、次に、レンジのブレイクアウトを監視し、未決済ポジションの管理やレンジの延長に関するロジックを開発する必要があります。また、レンジ座標を新しい値に更新することも考慮しなければなりません。

次に、理論で説明した通り、ブレイクアウトのインスタンスを特定します。もしブレイクアウトが確認されたら、マーケットポジションを建てる必要があります。これはすべてのティックでおこなう必要があるので、新しいバーの制限なしでおこないます。まず、それぞれの条件が満たされた時点でポジションを建てるために使用するask価格とbid価格を宣言します。これは、最新の価格相場を入手するために、ティックごとにおこなう必要があります。

   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); // Get and normalize the current Ask price
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); // Get and normalize the current Bid price

ここでは、最近の価格を格納するためにdoubleデータ型の変数を宣言し、精度を維持するために浮動小数点数を四捨五入して銘柄通貨の桁数に正規化します。 

すべてのティックでブレイクアウトをスキャンするためには、レンジが存在し、価格がレンジ内にある場合にのみ、プログラムがストレスを受けるうにするロジックが必要です。一般的に、市場は中程度から高いボラティリティの状態にあるため、もみ合いレンジの出現頻度は限られています。レンジが特定され、チャートの近接範囲内にある場合、価格は最終的にレンジをブレイクアウトする可能性が高くなります。これらの条件のいずれかが満たされたら、ブレイクアウトやさらなるレンジの更新を確認する必要はなく、冷静に別のレンジが特定されるのを待ちます。 

   if (isRangeExist && isInRange){ // If the range exists and we are in range

      ...

   }

ここでは、もみ合いレンジが存在するかどうかisRangeExistを確認し、現在の価格がこのレンジ内にあるかどうかisInRangeを確認します。両方の条件が当てはまる場合、ブレイクアウトの可能性のある価格を計算します。

      double R_HighBreak_Prc = (PRICE2_Y2+rangeSizePoints*_Point); // Calculate the high breakout price
      double R_LowBreak_Prc = (PRICE1_Y1-rangeSizePoints*_Point); // Calculate the low breakout price

高値ブレイクアウト価格を見つけるには、最低安値に許容される最大レンジサイズをポイント単位で加えます。この計算は、レンジサイズのポイントにポイントサイズを掛け合わせ、その結果を変数PRICE2_Y2に加え、最終値をR_HighBreak_Prcというdoubleデータ型の変数に格納することでおこなわれます。例えば、最安値が0.66773であり、レンジサイズのポイントが400、ポイント値が0.00001だと仮定します。この場合、400に0.00001を掛けると0.00400となり、これを最安値に加えると、0.66773 + 0.00400 = 0.67173となります。この計算結果は高値ブレイク価格に格納され、ask価格がこの値を上回った場合にブレイクアウトが定義され、市場価格との比較に使用されます。同様に、安値ブレイクアウト価格を決定するには、高値から許容される最大レンジサイズをポイント単位で差し引きます。この計算は、レンジサイズのポイントにポイントサイズを掛け、その結果を最高値から引き、最終値をR_LowBreak_Prc変数に格納します。

そして、ブレイクアウトを確認し、ブレイクアウトがあれば、それぞれのポジションを建てます。まず、市場価格が定義された高値ブレイクアウト価格を上抜けし、買いのチャンスを示唆する状況を扱います。

      if (Ask > R_HighBreak_Prc){ // If the Ask price breaks the high breakout price
         Print("BUY NOW, ASK = ",Ask,", L = ",PRICE2_Y2,", H BREAK = ",R_HighBreak_Prc); // Print a message to buy
         isInRange = false; isRangeExist = false; // Reset range flags
         if (PositionsTotal() > 0){return;} // Exit the function
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-sl_points*_Point,Bid+tp_points*_Point);
         return; // Exit the function
      }

まず、現在のask価格が高値ブレイクアウト価格より大きいかどうかを確認します。この条件がtrueであれば、市場価格がもみ合いレンジの上限を上抜いたことを示します。それに応答して、端末にメッセージを表示し、ブレイクアウトが発生したことを記録します。表示するメッセージには、現在のask価格、レンジの最安値、およびブレイクアウトした高値を含め、買いシグナルの背景情報を提供します。そして、isInRangeとisRangeExistのフラグをfalseにリセットし、現在のもみ合いレンジがもはや有効でないことを示します。これにより、この範囲に基づく今後の意思決定を停止できます。続いて、PositionsTotal関数を使って、現在の取引に既存のポジションがあるかどうかを確認します。これは、同じ銘柄に対して複数のポジションを同時に建てるのを避けるためのステップです。もしポジションが既に存在する場合、早めに関数を終了します。既存のポジションがない場合、CTradeオブジェクトobj_TradeのBuyメソッドを使って、新しい買いポジションを建てます。このメソッドには、取引数量、銘柄、エントリ価格(ask価格)、ストップロス価格、およびテイクプロフィット価格が指定されます。最後に、取引の開始が完了した後、関数を終了し、このティック内での追加のコード実行を防ぎます。 

市場価格が定義された安値ブレイクアウト価格を下回り、売りチャンスを示す状況を処理するために、以下のコードスニペットに示すように、同様の制御ロジックが採用されます。 

      else if (Bid < R_LowBreak_Prc){ // If the Bid price breaks the low breakout price
         Print("SELL NOW"); // Print a message to sell
         isInRange = false; isRangeExist = false; // Reset range flags
         if (PositionsTotal() > 0){return;} // Exit the function
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+sl_points*_Point,Ask-tp_points*_Point);
         return; // Exit the function
      }

まとめるとこうなります。

買いポジション確認

画像から明らかなように、レンジ(この場合は高値)を抜けた際に、ストップロスとテイクプロフィットをそれぞれ設定して買いポジションを建てます。エントリ条件や取引レベルは動的であり、個人の取引スタイルに合わせて柔軟に設定できます。例えば、極値やリスクとリターンの比率を基に、適切なレベルを選択できます。確認のため、ask価格が0.68313、安値が0.67911で、高値ブレイク価格が0.67911 + 0.00400 = 0.68311であるとします。このとき、現在のask価格が0.68313であり、計算上の高値0.68311を上回っているため、条件を満たし、現在のask価格で買いポジションが開始されます。 

現在、もみ合いレンジは固定されており、範囲(長方形)は固定されています。範囲を正しく設定しても、範囲オブジェクトが更新されないことがわかるでしょう。しかし、価格がオブジェクトレンジの価格を超えた場合にはレンジを更新する必要があります。もみ合いレンジが動的に反映されるためには、生成されたバーによって常に範囲を拡張するロジックを導入する必要があります。まず、現在のask価格が過去に記録したもみ合いレンジ内の高値を上回るシナリオを考慮します。この条件が満たされた場合、レンジの上限を新しい最高値に更新することが求められます。以下のコードスニペットで、もみ合いレンジの上限を更新するロジックを実現できます。

      if (Ask > PRICE1_Y1){ // If the Ask price is higher than the current high price
         PRICE1_Y1 = Ask; // Update the high price to the Ask price
         TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time
         Print("UPDATED RANGE PRICE1_Y1 TO ASK, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range
      }

もしask価格が以前に記録した高値を上回っていれば、PRICE1_Y1を現在のask価格に設定します。同時に、範囲「TIME2_Y2」の終了時刻を、iTime 関数を使用して取得した現在時刻に更新し、ターゲットバーのインデックスを現在のバー、0として渡します。このような調整を追跡し、明確にするために、範囲が更新され、再プロットが必要であることを示すメッセージを端末に表示します。その後、新しい高値と現在時刻を含む更新されたパラメータで「plotConsolidationRange」関数を呼び出し、チャート上に変化を視覚的に反映させます。

現在のbid価格が、もみ合いレンジ内で過去に記録された最安値を下回るというシナリオを処理するには、もみ合いレンジの下限を新しい最安値を反映するように更新する必要があることを示す、同様のアプローチが採用されます。

      else if (Bid < PRICE2_Y2){ // If the Bid price is lower than the current low price
         PRICE2_Y2 = Bid; // Update the low price to the Bid price
         TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time
         Print("UPDATED RANGE PRICE2_Y2 TO BID, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range
      }

これらの変更を追跡するために、更新がないインスタンスと、簡単に比較できるようにGIF(Graphics Interchange Format)の更新があるインスタンスを用意しましょう。

更新前:

更新前

更新後

更新後

最後に、ask価格が高値を上回らず、bid価格が安値を下回らないというシナリオも考えられます。その場合、直近で確定したバーを含めて、もみ合いレンジを拡大する必要があるでしょう。以下はそのためのコードスニペットです。

      else{
         if (isNewBar){ // If a new bar has appeared
            TIME2_Y2 = iTime(_Symbol,_Period,1); // Update the end time to the previous bar time
            Print("EXTEND THE RANGE TO PREV BAR TIME"); // Print a message indicating the range is extended
            plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range
         }
      }

isNewBarフラグを使って、新しいバーが生成されたかどうかを確認します。新しいバーが実際に出現した場合、統合範囲「TIME2_Y2」の終了時刻を、iTime関数を使用して取得した前のバーの時刻に更新し、ターゲットバーインデックスを1(現在のバーの前のバー)として渡します。この調整が明確で追跡可能になるよう、レンジの終了時刻が前のバーの時刻に延長されたことを示すメッセージを端末に表示します。その後、更新された終了時刻を含むパラメータを用いてplotConsolidationRange関数を呼び出し、チャート上に変更を視覚的に反映させます。

以下は、範囲内更新のマイルストーンの図解です。

範囲内更新

MQL5でもみ合いレンジブレイクアウト戦略ベースのEAを作成するための完全なソースコードは以下の通りです。

//+------------------------------------------------------------------+
//|                                 CONSOLIDATION RANGE BREAKOUT.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"


#include <Trade/Trade.mqh> // Include the trade library
CTrade obj_Trade; // Create an instance of the CTrade class

#define rangeNAME "CONSOLIDATION RANGE" // Define the name of the consolidation range

datetime TIME1_X1, TIME2_Y2; // Declare datetime variables to hold range start and end times
double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices

bool isRangeExist = false; // Flag to check if the range exists
bool isInRange = false; // Flag to check if we are currently within the range

int rangeBars = 10; // Number of bars to consider for the range
int rangeSizePoints = 400; // Maximum range size in points

double sl_points = 500.0; // Stop loss points
double tp_points = 500.0; // Take profit points

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   // Initialization code here (we don't initialize anything)
//---
   return(INIT_SUCCEEDED); // Return initialization success
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   // Deinitialization code here (we don't deinitialize anything)
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   
   int currBars = iBars(_Symbol,_Period); // Get the current number of bars
   static int prevBars = currBars; // Static variable to store the previous number of bars
   static bool isNewBar = false; // Static flag to check if a new bar has appeared
   if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed
   else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars
   
   if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared
      TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range
      int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range
      PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range
      
      TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time
      int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range
      PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range
      
      isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points
      
      if (isInRange){ // If the range size is valid
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range
         isRangeExist = true; // Set the range exist flag to true
         Print("RANGE PLOTTED"); // Print a message indicating the range is plotted
      }
   }
   
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); // Get and normalize the current Ask price
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); // Get and normalize the current Bid price
   
   if (isRangeExist && isInRange){ // If the range exists and we are in range
      double R_HighBreak_Prc = (PRICE2_Y2+rangeSizePoints*_Point); // Calculate the high breakout price
      double R_LowBreak_Prc = (PRICE1_Y1-rangeSizePoints*_Point); // Calculate the low breakout price
      if (Ask > R_HighBreak_Prc){ // If the Ask price breaks the high breakout price
         Print("BUY NOW, ASK = ",Ask,", L = ",PRICE2_Y2,", H BREAK = ",R_HighBreak_Prc); // Print a message to buy
         isInRange = false; isRangeExist = false; // Reset range flags
         if (PositionsTotal() > 0){return;} // Exit the function
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-sl_points*_Point,Bid+tp_points*_Point);
         return; // Exit the function
      }
      else if (Bid < R_LowBreak_Prc){ // If the Bid price breaks the low breakout price
         Print("SELL NOW"); // Print a message to sell
         isInRange = false; isRangeExist = false; // Reset range flags
         if (PositionsTotal() > 0){return;} // Exit the function
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+sl_points*_Point,Ask-tp_points*_Point);
         return; // Exit the function
      }
      
      if (Ask > PRICE1_Y1){ // If the Ask price is higher than the current high price
         PRICE1_Y1 = Ask; // Update the high price to the Ask price
         TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time
         Print("UPDATED RANGE PRICE1_Y1 TO ASK, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range
      }
      else if (Bid < PRICE2_Y2){ // If the Bid price is lower than the current low price
         PRICE2_Y2 = Bid; // Update the low price to the Bid price
         TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time
         Print("UPDATED RANGE PRICE2_Y2 TO BID, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted
         plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range
      }
      else{
         if (isNewBar){ // If a new bar has appeared
            TIME2_Y2 = iTime(_Symbol,_Period,1); // Update the end time to the previous bar time
            Print("EXTEND THE RANGE TO PREV BAR TIME"); // Print a message indicating the range is extended
            plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range
         }
      }
      
   }
   
}
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Function to plot the consolidation range                         |
//| rangeName - name of the range object                             |
//| time1_x1 - start time of the range                               |
//| price1_y1 - high price of the range                              |
//| time2_x2 - end time of the range                                 |
//| price2_y2 - low price of the range                               |
//+------------------------------------------------------------------+
void plotConsolidationRange(string rangeName,datetime time1_x1,double price1_y1,
   datetime time2_x2,double price2_y2){
   if (ObjectFind(0,rangeName) < 0){ // If the range object does not exist
      ObjectCreate(0,rangeName,OBJ_RECTANGLE,0,time1_x1,price1_y1,time2_x2,price2_y2); // Create the range object
      ObjectSetInteger(0,rangeName,OBJPROP_COLOR,clrBlue); // Set the color of the range
      ObjectSetInteger(0,rangeName,OBJPROP_FILL,true); // Enable fill for the range
      ObjectSetInteger(0,rangeName,OBJPROP_WIDTH,5); // Set the width of the range
   }
   else { // If the range object exists
      ObjectSetInteger(0,rangeName,OBJPROP_TIME,0,time1_x1); // Update the start time of the range
      ObjectSetDouble(0,rangeName,OBJPROP_PRICE,0,price1_y1); // Update the high price of the range
      ObjectSetInteger(0,rangeName,OBJPROP_TIME,1,time2_x2); // Update the end time of the range
      ObjectSetDouble(0,rangeName,OBJPROP_PRICE,1,price2_y2); // Update the low price of the range
   }
   ChartRedraw(0); // Redraw the chart to reflect changes
}


バックテスト結果

ストラテジーテスターでテストした結果は以下の通りです。

  • 残高/エクイティグラフ

グラフ

  • バックテスト結果

結果

  • 期間別取引項目

期間別貿易項目

結論

結論として、もみ合いブレイクアウト戦略の自動化は、必要な要素を理解すれば、想像ほど複雑ではないと言えるでしょう。技術的には、その作成には、戦略と実際の要件、つまり有効な戦略の設定を作成するために満たさなければならない目標を明確に理解することだけが必要であることがわかります。 

この記事では、もみ合いレンジブレイクアウトFX取引戦略を作成する際に考慮し、明確に理解する必要がある理論的な部分に焦点を当てています。ここには、戦略の青写真だけでなく、その定義や概要も含まれています。さらに、戦略のコーディングプロセスにおいては、ローソク足の分析、ボラティリティの低い期間の特定、その期間内での支持線と抵抗線の設定、ブレイクアウトの追跡、結果の視覚化、そして生成されたシグナルに基づく取引ポジションのオープンといった具体的なステップに重点を置いています。長期的には、これらのプロセスがもみ合いレンジブレイクアウト戦略の自動化を可能にし、より迅速な取引の実行と戦略の拡張性を促進することになるでしょう。

免責条項:この記事で説明されている情報は、あくまでも教育目的であり、プライスアクションアプローチに基づいたもみ合いレンジブレイクアウトEAの作成方法についての洞察を提供することを目的としています。そのため、より多くの最適化とデータの抽出を考慮した、より良いEAを作成するための出発点として活用することが求められます。提示された情報は、いかなる取引結果も保証するものではありません。

この記事が役に立ち、楽しく、理解しやすく、今後のEAの開発に活かせるようなものであったことを願っています。テクニカルな側面では、プライスアクションアプローチ、特にもみ合いレンジブレイクアウト戦略に基づく市場分析が、より円滑に行えるようになることを期待しています。お楽しみください。


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

EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5入門(第8回):初心者のためのEA構築ガイド(II) MQL5入門(第8回):初心者のためのEA構築ガイド(II)
この記事では、MQL5フォーラムでよく見られる初心者からの質問を取り上げ、実践的な解決策を紹介します。売買やローソク足の価格取得、取引限度額の設定、取引期間や利益/損失の閾値の管理といった基本的なタスクを自動売買で実行する方法を学びます。MQL5でのこれらの概念の理解と実装を強化するため、ステップごとのガイダンスも提供します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
初心者のためのMQL5におけるファンダメンタル分析とテクニカル分析戦略の組み合わせ 初心者のためのMQL5におけるファンダメンタル分析とテクニカル分析戦略の組み合わせ
この記事では、トレンドフォローとファンダメンタル分析の原則を1つのエキスパートアドバイザー(EA)にシームレスに統合し、より強固な取引戦略を構築する方法について説明します。MQL5を活用して、誰でも簡単にカスタマイズされた取引アルゴリズムを作成できることを紹介します。