English Deutsch
preview
Break of Structure (BoS)戦略のステップバイステップガイド

Break of Structure (BoS)戦略のステップバイステップガイド

MetaTrader 5トレーディング | 31 7月 2024, 09:44
14 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

この記事では、スマートマネーコンセプト(Smart Money Concept: SMC)の文脈で、マーケットのトレンドや方向性、FX取引戦略の大幅な転換を意味する用語であるBreak of Structure (BoS)と、それに基づくエキスパートアドバイザー(EA)の作成について説明します。

MetaTrader 5 (MT5)用のMetaQuotes言語5(MQL5)の定義、種類、取引戦略の応用、開発について、Break of Structureのニュアンスを掘り下げながら探っていきます。BoSの概念は、トレーダーがマーケットの動きを予測する能力を高め、より良い決断を下し、最終的にはリスク管理に熟達するために学ぶべき有用なツールです。以下の題材を使用して、上記のことを達成しましょう。

  1. Break of Structure (BoS)の定義
  2. Break of Structure (BoS)の説明
  3. Break of Structure (BoS)の種類
  4. 取引戦略の説明
  5. 取引戦略の設計図
  6. MetaQuotes言語5 (MQL5)による実装
  7. ストラテジーテスターの結果
  8. 結論

この旅では、ベースとなるIDEコーディング環境としてLanguage 5(MQL5)を広範囲に使用し、MetaTrader 5 (MT5)取引端末でファイルを実行します。 従って、前述のバージョンを持っていることが最も重要になります。では始めましょう。


Break of Structure (BoS)の定義

BoSは、スマートマネーコンセプト(SMC)を活用し、マーケットトレンドや方向性の重要な変化を特定するテクニカル分析の重要な概念です。一般的には、それまでの値動きによって形成されたスイング安値やスイング高値を価格が決定的に上抜けたときに発生します。価格がスイングの高値を上回ったり、スイングの安値を下回ったりすると、それまで形成されていた相場構造が崩れます。これは通常、マーケット心理やトレンドの方向性の変化を示し、既存のトレンドの継続や新たなトレンドの始まりを示唆します。


Break of Structure (BoS)の説明

BoSを効果的に説明するために、まず、スマートマネーコンセプトの他の要素である市場構造の変化(Market Structure Shift: MSS)と性質の変化(Change of Character: CHoCH)と区別しましょう。

  • Market Structure Shift (MSS)

MSSは、Market Momentum Shift (MMS)とも呼ばれ、下降トレンドの直近高値、または逆に上昇トレンドの直近高値を、それぞれ直近のスイング安値またはスイング高値をブレイクすることなくブレイクすることで発生します。これは構造の変化によるトレンドの反転を意味し、それゆえマーケット構造の「シフト」と呼ばれます。

Market Structure Shift

  • Change of Character (CHoCH)

一方、CHoCHは、下降トレンドでは直近のスイング安値を最初にブレイクした後に直近の高値をブレイクすることによって、上昇トレンドでは直近のスイング高値を最初にブレイクした後に直近の安値をブレイクすることによって起こります。

Change of Character

  • Break of Structure (BoS)

さて、マーケット構造に基づくスマートマネーコンセプトアプローチの3つの主要要素の主な違いがわかったところで、この記事のメインテーマであるそのブレイクについて掘り下げてみましょう。先に述べた定義から、BoSとは、古い高値または安値を突破して、それぞれ新しい高値または安値をつけることを意味することにお気づきでしょう。BoSの各インスタンスは、市場トレンドを上昇させて高値更新(Higher High: HH)と安値切り上げ(Higher Low: HL)を作り出すか、または下降させて新しい高値切り下げ(Lower High: LH)と安値更新(Lower Low: LL)を作り出し、通常、価格のスイング高値とスイング安値ポイントとして説明される市場トレンドを助けます。

Break of Structure

ブレイクはローソク足の終値でおこなわなければなりません。つまり、スイングハイをブレイクした場合、終値はスイングポイントを上回り、スイングローをブレイクした場合、終値はスイングポイントを下回ります。簡単に言えば、ローソク足またはバーの実体でのブレイクのみが有効なBoSと見なされます。つまり、ローソク髭でのブレイクは無効なBoSと見なされます。

  • 無効なBoS

無効なBoS

  • 有効なBoS

有効なBoS


Breaks of Structure (BoS)の種類

すでに述べたように、BoSはトレンド相場で発生します。つまり、上昇トレンドまたは下降トレンドのいずれかで発生します。このことはすでに、BoSが2種類しかないことを示唆します。

  • 強気BoS

これは、HHとHLを特徴とする上昇トレンドで発生します。テクニカル的には、BoSは、価格が上昇トレンドの直近の高値をブレイクし、新たな高値を形成することを意味します。

強気BoS

  • 弱気BoS

ここでは、LLとLHで構成される下降トレンドにおいて、弱気BoSが発生します。BoSは、価格が下降トレンドにおける直近の安値を更新し、新たな安値を形成することを意味します。

弱気BoS


取引戦略の説明

この戦略を使用して効果的に取引するには一連の手順が必要ですが、心配はいりません。順を追って説明しましょう。 

より上位の時間枠(HTF)に基礎を置く: まず、包括的な分析のためには、マーケット動向の全体的な概観を提供するため、選択した資産のより上位の時間枠を調べます。マーケットの長期的な軌道を明らかにする傾向があるため、4時間足や日足の時間枠を含めることができます。下位の時間枠は、操作や流動性の掃引、ほろ酔い運転手のようなジグザグ走行によって多くのスイングポイントを含み、結果的に取るに足らないブレイクが発生するため、使用を避けます。

マーケットの基調を見極める:第二に、現在のマーケットトレンドをチャート上で確認する必要があります。上昇トレンドは、価格行動の高値と安値の上昇パターンを含み、下降トレンドは、安値と高値の下降パターンで構成されます。

エントリポイントを特定します。より高い時間枠で現在のトレンドを確認した後、スイングハイまたはスイングローソク足のブレイクでマーケットに侵入することができます。 ローソク足が強ければ強いほど、シグナル確認は心強くなります。

上昇トレンドの例

上昇トレンドの例

下降トレンドの例

下降トレンドの例

5分足のような下位時間枠では、需給、相対力指数(RSI)やMACD(移動平均収束ダイバージェンス)のようなテクニカル指標、または巻き込みパターンやインサイドバーパターンのような日本のローソク足パターンのような特別な確認戦略を使用することができます。

出口を特定します。マーケットに参入する際には、リスクを管理しながらマーケットから撤退するための堅実な戦略も必要です。ストップロスについては、ポジションのエントリポイントに近く、かつ意味のある利食いを残せるのであれば、直前のスイングポイントに置きます。そうでない場合は、固定pipsのリスク対リターンの比率を使用します。逆に、次のスイングポイントで利食いするのですが、利食い水準の将来のスイングポイントを決めるのは難しいので、リスクリワードレシオを利食いの目安にします。

取引戦略の設計図

私たちが伝えたコンセプトを簡単に理解するために、設計図で視覚化してみましょう。

強気BoS

強気BoS設計図

弱気BoS

弱気BoS設計図


MetaTrader 5 (MT5)用MetaQuotes言語5 (MQL5)での実装

BoS取引戦略に関するすべての理論を学んだ後は、その理論を自動化し、MetaTrader 5用のMetaQuotes言語5 (MQL5)でEAを作成しましょう。

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

メタクォーツを開く

MetaEditorを開いたら、[新規]をクリックし、出てきたウィザードで[EA(テンプレート)]をチェックし、[次へ]をクリックします。

新規EAファイルの作成

ファイル名

次に、希望するEAファイル名を入力し、[次へ]を、もう一度[次へ]をクリックし、[完了]をクリックします。ここまでくれば、BoS戦略をコーディングし、プログラムする準備が整ったことになります。

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

#include <Trade/Trade.mqh>
CTrade obj_Trade;

ほとんどのアクティビティはOnTickイベントハンドラで実行されます。純粋なプライスアクションなので、指標ハンドルの初期化にOnInitイベントハンドラを使用する必要はありません。このように、コード全体はOnTickイベントハンドラでのみ実行されます。まず、このコードの中心となる、関数が関数の機能以外に受け取るパラメータを見てみましょう。

void OnTick(){

}

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

ここまでで、価格相場が変化するたびにOnTick関数が生成されることがわかりました。次に、特定のコードスニペットの実行を制御するために後で使用する制御ロジックを定義する必要があります。コード スニペットはバーごとに1回実行され、ティックごとに実行されることはありません。これにより、少なくとも不要なコードの実行が回避され、デバイスメモリが節約されます。これは、スイングの高値と安値を探すときに必要になります。各ティックごとに検索する必要はなく、同じローソク足であれば常に同じ結果が得られます。ロジックはこうです。

   static bool isNewBar = false;
   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars){isNewBar = false;}
   else if (prevBars != currBars){isNewBar = true; prevBars = currBars;}

まず、isNewBarという名前の静的ブール変数を宣言し、値falseで初期化します。この変数の目的は、チャート上に新しいバーが形成されたかどうかを追跡することです。関数のライフタイムを通じて値を保持できるように、staticとキーワードでローカル変数を宣言します。 つまり、ダイナミックではないのです。通常、この変数は、後でtrueに変更しない限り、常にfalseに等しく、変更されてもその値は保持され、次のティックで更新されることはありません。動的の場合は常に初期値に更新されます。

次に、指定された取引銘柄と期間、または聞いたことがあるかもしれませんが、時間枠のチャート上の現在のバーの計算された数を格納する別の整数変数currBarsを宣言します。これは、iBars関数を使用することで実現できます。iBars関数は2つの引数、つまり銘柄と期間だけを取ります。

ここでも、新しいバーが生成されたときに、チャート上の前のバーの合計数を格納するために、別の静的整数変数 prevBarsを宣言し、やはり関数の最初の実行のために、チャート上の現在のバーの値で初期化します。現在のバー数と前回のバー数を比較し、チャート上で新しいバーが生成されるインスタンスを決定するために使用します。

最後に、条件文を使用して、現在のバー数と前回のバー数が等しいかどうかを確認します。等しい場合は、新しいバーが形成されていないことを意味するので、isNewBar変数はfalseのままです。そうでなければ、現在のバーカウントと前のバーカウントが等しくなければ、新しいバーが形成されたことを示しますl。この場合、isNewBar変数をtrueに設定し、prevBarsを現在のバーカウントと一致するように更新します。したがって、このコードスニペットで、新しいバーが形成されたかどうかを追跡し、後でその結果を使用して、1つのバーにつき1回しかインスタンスを実行しないようにすることができます。

これで、チャート上でスイングポイントを探すことができます。ポイントにはさまざまなスキャンが必要です。特定のバーを選択し、隣接するすべてのバーをスキャンすることによって、これを達成することを計画しています。もちろん、事前に定義されたバーの範囲内で、右と左に、現在のバーがスイングハイの場合は範囲内で最高値であるかどうかを決定し、スイングローの場合は最低値です。そこでまず、このロジックを格納するために必要な変数を定義しましょう。

   const int length = 20;
   const int limit = 20;

ここでは、2つの整数変数lengthとlimitを宣言します。Lengthは、スイングの高値と安値を特定する際に考慮されるバーの範囲を表し、Limitは、その特定のインスタンスでスキャンされている現在のバーのインデックスを表します。例えば、スイングハイかどうかを識別するために、インデックス10のバーをスキャンするために選択したとします。次に、右と左に隣接するすべてのバーを経由してループし、インデックス10にある現在のバーより高い別のバーがあるかどうかを調べます。したがって、左のバーは現在のバーの前のバーであり、インデックス(リミット、これは10に等しい、+1)11で見つかります。右に進む場合も同じです。

デフォルトでは、変数を20に初期化します。また、定数にするためにconstと宣言していることにもお気づきでしょう。これは、プログラムの実行中、その値が固定されたままであることを保証するためにおこなわれます。その結果、異なるバー間でスイングポイントの分析範囲を同じに保つのに役立つ一貫性が生まれます。値を一定に保つことは、プログラム実行中に誤って変数を変更してしまうのを防ぐのにも役立ちます。

次に、プログラムの他の重要な変数を素早く定義しましょう。分析中の現在のバーを追跡し、あらかじめ定義された範囲内の近隣のバーとの関係を評価する必要があります。そのためには、以下の変数を宣言します。

   int right_index, left_index;
   bool isSwingHigh = true, isSwingLow = true;
   static double swing_H = -1.0, swing_L = -1.0;
   int curr_bar = limit;

まず2つの整数変数right_indexと left_indexを宣言し、隣り合うバーのインデックスを管理します。右のインデックスは現在のバーの右隣のバーのインデックスを表し、左のインデックスは現在のバーの左隣のバーのインデックスを表します。これは、分析のために選択されたバーです。ここでも2つのブール変数isSwingHighとisSwingLowを宣言し、それぞれ現在のバーが潜在的なスイングの高値か安値かを判断するフラグとして使用し、それらをtrueに初期化します。分析後、いずれかのフラグがtrueのままであれば、スイングポイントの存在を示します。さらに、static double変数「swing_H」と「swing_L」を宣言し、それぞれスイングの高値と安値の価格水準を格納します。スイングの高値または安値がまだ検出されていないことを示すために、これらの値を-1で初期化します。これらはstaticとして作成します。スイングポイントが取得されると、スイングポイントは変更されず、将来の参照用に保存され、構造の変更によってスイングポイントが壊れた場合に後で識別できるようにします。BoSがあれば-1に変更されるか、新たに生成されるスイングポイントに置き換えられます。最後に、分析の開始点を決定する curr_bar変数があります。

ここまでで、プログラムにとって重要な変数はすべて完璧かつ十分に宣言できたので、分析ループを開始することができます。スイングポイントを分析しマッピングするには、1バーにつき1回だけおこなえば大丈夫です。したがって、スイングポイントの分析は1つのバーにつき1回しかおこなわれません。ここで、「isNewBar」変数が役に立ちます。

   if (isNewBar){ ... }

次に、スイングの高値と安値を見つけるためにforループをインスタンス化します。

      for (int j=1; j<=length; j++){
         right_index = curr_bar - j;
         left_index = curr_bar + j;
         if ( (high(curr_bar) <= high(right_index)) || (high(curr_bar) < high(left_index)) ){
            isSwingHigh = false;
         }
         if ( (low(curr_bar) >= low(right_index)) || (low(curr_bar) > low(left_index)) ){
            isSwingLow = false;
         }
      }

ループ整数変数「j」を宣言し、現在のバーとその隣のバーを比較する際に考慮するバーの数を表します。次に、現在のバーから「j」を引いて、現在のバーの右隣のバーのインデックスを計算します。同じロジックを使用して、現在のバーに「j」を足すことで、左側の隣のバーのインデックスを得ます。視覚的な理由から結果を出力すると、次のようになります。

バーインデックス

print文は、以下の組み込み関数を使用することで実現しました。

         Print("Current Bar Index = ",curr_bar," ::: Right index: ",right_index,", Left index: ",left_index);

ここまでで、選択したバーインデックス(この場合は20)に対して、指定された長さの範囲内で左右に隣接するすべてのバーを評価することが明確です。各反復で、右に1つ引き、左に1つ足します。その結果、右のインデックスは値ゼロになり、典型的には現在のバーを意味し、左のインデックスはあらかじめ定義された長さの2倍になります。バーの評価が正しくおこなわれたことを確認したところで、各反復におけるスイングポイントの有無を判断します。 

スイングの高値があるかどうかを判断するには、条件文を使用して、現在のバーの高値が右インデックスのバーの高値以下か、左インデックスのバーの高値以下かを確認します。どちらかの条件がtrueであれば、現在のバーの高値が隣のバーより高くないことを意味するので、isSwingHighはfalseに設定されます。スイングローがあるかどうかを判断するには、同じロジックが優先されますが、条件は逆です。

ループが終了するまでに、isSwingHighがまだtrueであれば、現在のバーが長さの範囲内で周囲のバーよりも高い高値を持っていることを示唆し、潜在的なスイングハイを示します。同じロジックがスイングローフラッグにも適用されます。もしtrueなら、スイングポイント変数にそれぞれの価格を入れ、スイングポイントを描画します。

      if (isSwingHigh){
         swing_H = high(curr_bar);
         Print("UP @ BAR INDEX ",curr_bar," of High: ",high(curr_bar));
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),high(curr_bar),77,clrBlue,-1);
      }
      if (isSwingLow){
         swing_L = low(curr_bar);
         Print("DOWN @ BAR INDEX ",curr_bar," of Low: ",low(curr_bar));
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),low(curr_bar),77,clrRed,1);
      }

スイング高値の高値とスイング安値の安値を取得するにはカスタム関数を使用します。関数は以下のように宣言されています。

double high(int index){return (iHigh(_Symbol,_Period,index));}
double low(int index){return (iLow(_Symbol,_Period,index));}
double close(int index){return (iClose(_Symbol,_Period,index));}
datetime time(int index){return (iTime(_Symbol,_Period,index));}

高値関数は、価格データシリーズ内のバーのインデックスを表す1つのパラメータまたは引数を取り、指定されたインデックスにおける指定されたバーの高値を取得します。同じロジックが、安値、終値、time関数にも適用されます。

チャート上のスイングポイントをそれぞれのバーに描画して視覚化するために、以下のカスタム関数を使用します。

void drawSwingPoint(string objName,datetime time,double price,int arrCode,
   color clr,int direction){
   
   if (ObjectFind(0,objName) < 0){
      ObjectCreate(0,objName,OBJ_ARROW,0,time,price);
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrCode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,10);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
      
      string txt = " BoS";
      string objNameDescr = objName + txt;
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time,price);
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
   }
   ChartRedraw(0);
}

カスタム関数drawSwingPointは、再利用を容易にするために6つのパラメータを取ります。パラメータは以下の通りです。

  • objName:作成するグラフィカルオブジェクトの名前を表す文字列
  • time:オブジェクトが配置されるべき時間座標を示すdatetime値
  • price:オブジェクトを配置する価格座標を表すdouble値
  • arrCode:矢印オブジェクトの矢印コードを指定する整数
  • clr:グラフィカルオブジェクトの色値(例:clrBlue、clrRed)
  • direction:テキストラベルを配置する方向(上または下)を示す整数

この関数は、まず、指定されたobjNameを持つオブジェクトがチャート上に既に存在するかどうかを確認します。存在しない場合は、オブジェクトの作成に進みます。オブジェクトの作成は、内蔵のObjectCreate関数を使用することによって達成されます。この関数は、描画するオブジェクト(この場合、OBJ_ARROWとして識別される矢印オブジェクト)の指定と、オブジェクト作成ポイントの座標を形成する時間と価格を必要とします。その後、オブジェクトのプロパティの矢印コード、色、フォントサイズ、アンカーポイントを設定します。矢印のコードについては、MQL5にはwingdingsフォントのあらかじめ定義された文字がいくつかあり、それを直接使用することができます。以下は文字を指定した表です。

矢印コード

ここまでは、以下のように指定された矢印をチャートに描くだけでです。

説明なしスイングポイント

指定した矢印コード(この場合は矢印コード77)でスイングポイントを描画できたことがわかりますが、その説明はありません。したがって、それぞれの説明を追加するために、矢印とテキストを連結します。OBJ_TEXTとして指定された別のテキストオブジェクトを作成し、それぞれのプロパティを設定します。テキストラベルは、スイングポイントに関する追加的な文脈や情報を提供することによって、スイングポイントに関連する説明的な注釈として機能し、トレーダーやアナリストにとってより有益な情報となります。テキストの値は、スイングポイントであることを意味するBoSとしました。

変数objNameDescrは、元のobjNameと説明テキストを連結して作られます。この組み合わせ名によって、矢印とそれに関連するテキストラベルが確実にリンクされます。この特定のコードスニペットはそのために使用されます。

      string txt = " BoS";
      string objNameDescr = objName + txt;
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time,price);
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }

これが、スイングポイントとその説明を連結した結果です。

解説付きスイングポイント

棒グラフの分析、スイングの高値と安値の識別、データの文書化、およびチャートのスイングポイントへのオブジェクトのマッピングを担当する完全なコードは以下のとおりです。

   if (isNewBar){
      for (int j=1; j<=length; j++){
         right_index = curr_bar - j;
         left_index = curr_bar + j;

         if ( (high(curr_bar) <= high(right_index)) || (high(curr_bar) < high(left_index)) ){
            isSwingHigh = false;
         }
         if ( (low(curr_bar) >= low(right_index)) || (low(curr_bar) > low(left_index)) ){
            isSwingLow = false;
         }
      }
      
      if (isSwingHigh){
         swing_H = high(curr_bar);
         Print("UP @ BAR INDEX ",curr_bar," of High: ",high(curr_bar));
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),high(curr_bar),77,clrBlue,-1);
      }
      if (isSwingLow){
         swing_L = low(curr_bar);
         Print("DOWN @ BAR INDEX ",curr_bar," of Low: ",low(curr_bar));
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),low(curr_bar),77,clrRed,1);
      }
   }

次に、理論部分で説明したように、スイングポイントのブレイクのインスタンスを特定し、インスタンスが存在する場合、ブレイクとオープンマーケットポジションをそれぞれ可視化するだけです。これはすべてのティックでおこなう必要があるので、新しいバーの制限なしでおこないます。まず、それぞれの条件が満たされた時点でポジションを建てるために使用するAsk価格とBid価格を宣言します。これは、最新の価格相場を入手するために、ティックごとにおこなう必要があります。

   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

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

価格が上昇し、スイング高値水準をブレイクしたかどうかを判断するには、条件文を使用します。まず、スイングの高値が存在するかどうかを確認します。スイングの高値はゼロより大きいというロジックで、すでに存在しないスイングの高値をブレイクすることはできないからです。そして、スイングハイポイントがすでにある場合は、買値がスイングハイレベルの上にあることを確認し、買値で買いポジションが建てられ、買値に関連するストップロスやテイクプロフィットなどの取引レベルが、ブレイクレベルの上のポイントに正しくマッピングされていることを確認します。最後に、前のバーの終値がスイングハイレベルを上回っているかどうかをチェックし、条件を満たす有効なブレイクがあることを確認します。すべての条件が満たされれば、有効なBoSができ、そのインスタンスを操作ログに出力します。

   if (swing_H > 0 && Bid > swing_H && close(1) > swing_H){
      Print("BREAK UP NOW");
      ...
   }

ブレイクの設定を視覚化するには、スイングハイのポイントからブレイクが発生するローソク足までスパンする矢印を描く必要があります。つまり、作成する矢印の2点(通常はスイングポイントに接続される矢印の始点と、ブレイクが発生するローソク足である矢印の終点)の座標が2つ必要になります。これは、以下のような画像で表すとわかりやすくなります。

ポイント座標

必要な2つの座標は、XとしてX軸に示される時間と、YとしてY軸に示される価格です。2つ目の座標、つまりBoSが発生したローソク足を取得するには、現在のバーインデックスを使用します。これは典型的に0です。ただし、スイングハイポイントを含むバーのインデックスを取得するのは少し難しいです。スイングハイのローソク足の価格だけを保存したことを思い出してください。価格を保存するのと同時にバーインデックスを保存することもできますが、その後に新しいバーが生成されるため、まったく意味がありません。これは、スイングポイントを含むバーのインデックスを見つけることができないという意味ではありません。前のバーの高値をループし、スイングの高値ポイントに一致するものを見つけることができます。以下はその方法です。

      int swing_H_index = 0;
      for (int i=0; i<=length*2+1000; i++){
         double high_sel = high(i);
         if (high_sel == swing_H){
            swing_H_index = i;
            Print("BREAK HIGH @ BAR ",swing_H_index);
            break;
         }
      }

まず、スイングハイインデックスを保持する整数変数swing_H_indexを宣言し、ゼロに初期化します。次に、forループを使用して、あらかじめ定義されたすべてのバーの二倍に加え、スイングポイントを見つけることができる任意のバー数である1000のバー範囲をループし、選択したバーの高値と保存されたスイングハイを比較します。そこで、もし一致するものが見つかれば、そのインデックスを保存し、スイングハイバーのインデックスはすでに見つかっているので、ループから早々に抜け出します。 

スイングハイバーインデックスを使用すると、バーのプロパティを取得できるようになります。この場合、矢印の開始点のx座標をマークする時間のみに関心があります。矢印のコードをマッピングするために使用した前回の関数と大差ないカスタム関数を使用します。

void drawBreakLevel(string objName,datetime time1,double price1,
   datetime time2,double price2,color clr,int direction){
   if (ObjectFind(0,objName) < 0){
      ObjectCreate(0,objName,OBJ_ARROWED_LINE,0,time1,price1,time2,price2);
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_WIDTH,2);
      
      string txt = " Break   ";
      string objNameDescr = objName + txt;
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time2,price2);
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
   }
   ChartRedraw(0);
}

以下は、前回との関数の違いです。

  1. 関数名をdrawBreakLevelと宣言します。
  2. 作成するオブジェクトは、OBJ_ARROWED_LINEとして識別される矢印付きのラインです。
  3. 矢印の線は2つの座標を含み、最初の座標は時間1と価格1、2番目の座標は時間2と価格2です。
  4. 連結テキストは「Break」で、BoSが発生したことを示します。

次に、この関数を使用して、チャート上にブレイクレベルの矢印付きラインを引きます。2番目の座標の2時間目には1を足すだけで、正確には現在のバーの1つ前のバーに到達します。そして、スイングハイ変数の値を-1にリセットし、すでに構造が壊れており、設定がもう存在しないことを示します。これは、スイングハイのポイントをすでにブレイクしているため、直前のティックでスイングハイのブレイクを探すのを避けるのに役立ちます。したがって、次のスイングハイポイントが形成されるのを待つだけで、変数が再び満たされ、ループが続きます。

      drawBreakLevel(TimeToString(time(0)),time(swing_H_index),high(swing_H_index),
      time(0+1),high(swing_H_index),clrBlue,-1);
      
      swing_H = -1.0;

最後に、スイングハイポイントをブレイクしたら買いポジションを建てます。 

      //--- Open Buy
      obj_Trade.Buy(0.01,_Symbol,Ask,Bid-500*7*_Point,Bid+500*_Point,"BoS Break Up BUY");
      
      return;

オブジェクトobj_Tradeとドット演算子を使用して、クラスに含まれるすべてのメソッドにアクセスします。この場合、買うだけでよいので、Buyメソッドを使い、出来高、取引レベル、取引コメントを提供します。最後に、すべてが設定され、実行するコードがなくなったので、戻ります。ただし、さらにコードがある場合は、現在の関数の実行を終了し、呼び出し元のプログラムに制御を返すため、return演算子の使用は避けてください。BoSを見つけ、矢印の線を引き、買いポジションを建てるための完全なコードは以下の通りです。

   if (swing_H > 0 && Bid > swing_H && close(1) > swing_H){
      Print("BREAK UP NOW");
      int swing_H_index = 0;
      for (int i=0; i<=length*2+1000; i++){
         double high_sel = high(i);
         if (high_sel == swing_H){
            swing_H_index = i;
            Print("BREAK HIGH @ BAR ",swing_H_index);
            break;
         }
      }
      drawBreakLevel(TimeToString(time(0)),time(swing_H_index),high(swing_H_index),
      time(0+1),high(swing_H_index),clrBlue,-1);
      
      swing_H = -1.0;
      
      //--- Open Buy
      obj_Trade.Buy(0.01,_Symbol,Ask,Bid-500*7*_Point,Bid+500*_Point,"BoS Break Up BUY");
      
      return;
   }

スイング安値のブレイク、矢印のブレイクラインの同時描画、および売りポジションのオープンについては、逆の条件でのみ同じロジックが優先されます。全コードは以下の通りです。

   else if (swing_L > 0 && Ask < swing_L && close(1) < swing_L){
      Print("BREAK DOWN NOW");
      int swing_L_index = 0;
      for (int i=0; i<=length*2+1000; i++){
         double low_sel = low(i);
         if (low_sel == swing_L){
            swing_L_index = i;
            Print("BREAK LOW @ BAR ",swing_L_index);
            break;
         }
      }
      drawBreakLevel(TimeToString(time(0)),time(swing_L_index),low(swing_L_index),
      time(0+1),low(swing_L_index),clrRed,1);

      swing_L = -1.0;
      
      //--- Open Sell
      obj_Trade.Sell(0.01,_Symbol,Bid,Ask+500*7*_Point,Ask-500*_Point,"BoS Break Down SELL");

      return;
   }

これがそのマイルストーンです。

マイルストーン

以下は、ブレイクとオープンポジションをそれぞれ識別する、MQL5でBoS外国為替取引戦略を作成するために必要な完全なコードです。

//+------------------------------------------------------------------+
//|                                                          BOS.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>
CTrade obj_Trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){return(INIT_SUCCEEDED);}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   
   static bool isNewBar = false;
   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars){isNewBar = false;}
   else if (prevBars != currBars){isNewBar = true; prevBars = currBars;}
   
   const int length = 5;
   const int limit = 5;

   int right_index, left_index;
   bool isSwingHigh = true, isSwingLow = true;
   static double swing_H = -1.0, swing_L = -1.0;
   int curr_bar = limit;
   
   if (isNewBar){
      for (int j=1; j<=length; j++){
         right_index = curr_bar - j;
         left_index = curr_bar + j;
         //Print("Current Bar Index = ",curr_bar," ::: Right index: ",right_index,", Left index: ",left_index);
         //Print("curr_bar(",curr_bar,") right_index = ",right_index,", left_index = ",left_index);
         // If high of the current bar curr_bar is <= high of the bar at right_index (to the left),
         //or if it’s < high of the bar at left_index (to the right), then isSwingHigh is set to false
         //This means that the current bar curr_bar does not have a higher high compared
         //to its neighbors, and therefore, it’s not a swing high
         if ( (high(curr_bar) <= high(right_index)) || (high(curr_bar) < high(left_index)) ){
            isSwingHigh = false;
         }
         if ( (low(curr_bar) >= low(right_index)) || (low(curr_bar) > low(left_index)) ){
            isSwingLow = false;
         }
      }
      //By the end of the loop, if isSwingHigh is still true, it suggests that 
      //current bar curr_bar has a higher high than the surrounding bars within
      //length range, marking a potential swing high.
      
      if (isSwingHigh){
         swing_H = high(curr_bar);
         Print("UP @ BAR INDEX ",curr_bar," of High: ",high(curr_bar));
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),high(curr_bar),77,clrBlue,-1);
      }
      if (isSwingLow){
         swing_L = low(curr_bar);
         Print("DOWN @ BAR INDEX ",curr_bar," of Low: ",low(curr_bar));
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),low(curr_bar),77,clrRed,1);
      }
   }
   
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

   if (swing_H > 0 && Bid > swing_H && close(1) > swing_H){
      Print("BREAK UP NOW");
      int swing_H_index = 0;
      for (int i=0; i<=length*2+1000; i++){
         double high_sel = high(i);
         if (high_sel == swing_H){
            swing_H_index = i;
            Print("BREAK HIGH @ BAR ",swing_H_index);
            break;
         }
      }
      drawBreakLevel(TimeToString(time(0)),time(swing_H_index),high(swing_H_index),
      time(0+1),high(swing_H_index),clrBlue,-1);
      
      swing_H = -1.0;
      
      //--- Open Buy
      obj_Trade.Buy(0.01,_Symbol,Ask,Bid-500*7*_Point,Bid+500*_Point,"BoS Break Up BUY");
      
      return;
   }
   else if (swing_L > 0 && Ask < swing_L && close(1) < swing_L){
      Print("BREAK DOWN NOW");
      int swing_L_index = 0;
      for (int i=0; i<=length*2+1000; i++){
         double low_sel = low(i);
         if (low_sel == swing_L){
            swing_L_index = i;
            Print("BREAK LOW @ BAR ",swing_L_index);
            break;
         }
      }
      drawBreakLevel(TimeToString(time(0)),time(swing_L_index),low(swing_L_index),
      time(0+1),low(swing_L_index),clrRed,1);

      swing_L = -1.0;
      
      //--- Open Sell
      obj_Trade.Sell(0.01,_Symbol,Bid,Ask+500*7*_Point,Ask-500*_Point,"BoS Break Down SELL");

      return;
   }
   
}
//+------------------------------------------------------------------+

double high(int index){return (iHigh(_Symbol,_Period,index));}
double low(int index){return (iLow(_Symbol,_Period,index));}
double close(int index){return (iClose(_Symbol,_Period,index));}
datetime time(int index){return (iTime(_Symbol,_Period,index));}

void drawSwingPoint(string objName,datetime time,double price,int arrCode,
   color clr,int direction){
   
   if (ObjectFind(0,objName) < 0){
      ObjectCreate(0,objName,OBJ_ARROW,0,time,price);
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrCode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,10);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
      
      string txt = " BoS";
      string objNameDescr = objName + txt;
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time,price);
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
   }
   ChartRedraw(0);
}

void drawBreakLevel(string objName,datetime time1,double price1,
   datetime time2,double price2,color clr,int direction){
   if (ObjectFind(0,objName) < 0){
      ObjectCreate(0,objName,OBJ_ARROWED_LINE,0,time1,price1,time2,price2);
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_WIDTH,2);
      
      string txt = " Break   ";
      string objNameDescr = objName + txt;
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time2,price2);
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER);
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);
      }
   }
   ChartRedraw(0);
}

お疲れ様です。BoSFX取引戦略に基づいて、取引シグナルを生成するだけでなく、生成されたシグナルに基づいてマーケットポジションを建てる、スマートマネーのコンセプト取引システムを開発しました。


ストラテジーテスターの結果

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

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

グラフ

  • バックテスト結果

結果


結論

結論としてBreak of Structure (BoS)戦略の自動化は、一度必要なことを考えれば、認識されているほど複雑ではないと自信を持って言えます。技術的には、その作成には、戦略と実際の要件、つまり有効な戦略の設定を作成するために満たさなければならない目標を明確に理解することだけが必要であることがわかります。 

全体として、この記事は、BoS外国為替取引戦略を作成するために考慮され、明確に理解されなければならない理論的な部分を強調しています。これには設計図のほかに、その定義、説明、タイプが含まれます。さらに、ストラテジーのコード化では、ローソク足の分析、スイングポイントの特定、ブレイクの追跡、出力の視覚化、生成されたシグナルに基づく取引ポジションのオープンといったステップを強調しています。長期的には、BoS戦略の自動化が可能になり、戦略の迅速な実行と拡張性が促進されます。

免責条項: この記事で説明されている情報は、あくまでも教育目的です。これは、スマートマネーコンセプトのアプローチに基づき、Break of Structure (BoS) EAを作成する方法についての洞察を示すことを目的としており、したがって、より多くの最適化とデータ抽出を考慮に入れた、より優れたEAを作成するためのベースとして使用する必要があります。提示された情報は、いかなる取引結果も保証するものではありません。

この記事が役に立ち、楽しく、理解しやすく、今後のEAの開発に活かせるようなものであったことを願っています。技術的には、スマートマネーコンセプト(SMC)アプローチ、特にBoS戦略に基づくマーケット分析が容易になります。


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

Candlestick Trend Constraintモデルの構築(第4回):トレンドの波ごとに表示スタイルをカスタマイズ Candlestick Trend Constraintモデルの構築(第4回):トレンドの波ごとに表示スタイルをカスタマイズ
この記事では、Meta Trader 5上で様々な指標のスタイルを描画するための強力なMQL5言語の機能を探ります。また、スクリプトと、スクリプトをモデルでどのように使えるかについても見ていきます。
MetaTrader 5で隠れマルコフモデルを統合する MetaTrader 5で隠れマルコフモデルを統合する
この記事では、Pythonを使用して学習した隠れマルコフモデルをMetaTrader 5アプリケーションに統合する方法を示します。隠れマルコフモデルは、時系列データをモデル化するために使用される強力な統計的ツールであり、モデル化されるシステムは観測不可能な(隠れた)状態によって特徴付けられます。HMMの基本的な前提は、ある時刻にある状態にある確率は、その前のタイムスロットにおけるプロセスの状態に依存するということです。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
知っておくべきMQL5ウィザードのテクニック(第22回):条件付きGAN 知っておくべきMQL5ウィザードのテクニック(第22回):条件付きGAN
敵対的生成ネットワーク(GAN: Generative Adversarial Network)は、より正確な結果を得るために、互いに訓練し合うニューラルネットワークのペアです。ExpertSignalクラスにおける金融時系列の予測への応用の可能性を考慮し、これらのネットワークの条件型を採用します。