English Русский 中文 Español Deutsch Português
preview
フェアバリューギャップ(FVG)/不均衡取引方法をステップバイステップで学ぶ:スマートマネーコンセプトのアプローチ

フェアバリューギャップ(FVG)/不均衡取引方法をステップバイステップで学ぶ:スマートマネーコンセプトのアプローチ

MetaTrader 5トレーディング | 31 5月 2024, 09:40
1 024 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

この記事では、フェアバリューギャップ(FVG)/不均衡戦略とスマートマネーコンセプトのアプローチに基づくEAの作成と開発に向けた基本的なステップについて説明します。本質的に、フェアバリューギャップ戦略に根ざしたEAを作成するための旅は、芸術と科学の融合です。通常、トレーダーには、ローソク足を分析するだけでなく、コンセプトレベルを視覚化するために描画する能力が必要となります。スマートマネーの謎を解き明かし、アルゴリズム取引の領域でその変革力を活用する探求に乗り出しましょう。以下のトピックを通じて、フェアバリューギャップEAを作成します。

  1. 不均衡の定義
  2. 取引戦略の説明
  3. 取引戦略の設計図
  4. MQL5取引システム
  5. ストラテジーテスターの結果
  6. 結論


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


フェアバリューギャップ(FVG)/不均衡の定義

フェアバリューギャップは、買い圧力と売り圧力の両方による不均衡の差を示します。一般的には、ボラティリティが高く、ベアとブルのどちらかが市場から完全に退場し、市場圧力が不均等になったときに発生します。これは通常、市場が一方向に大きく動くことを特徴とします。通常、チャート上には長いローソク足が表示されます。このような知識があれば、フェアバリューギャップを利用するさまざまなアプローチを採用し、取引戦略を立てることができます。

フェアバリューギャップ(FVG)取引戦略の説明

フェアバリューギャップ取引戦略は、フェアバリュー評価の概念とローソク足の不均衡を統合し、潜在的な取引機会を特定します。

ここでは、ローソク足の不均衡を取り入れたフェアバリューギャップ取引戦略について詳しく説明します。

  • トレーダーは包括的なファンダメンタルズ分析をおこない、フェアバリューギャップを判断します。これは、ローソク足のパターンを使用して市場の値動きを分析し、買い圧力と売り圧力の不均衡を特定することによって達成されます。強気の包み足、弱気の包み足、同時線などのローソク足パターンは、市場のセンチメントやモメンタムの潜在的な変化についての洞察を提供します。トレーダーは、市場価格が推定フェアバリューから大きく乖離している事例を探すと同時に、顕著なローソク足の不均衡を観察します。前セッションの終値と現在のセッションの始値の間に大きなギャップがあり、強気または弱気のローソク足パターンがある場合、フェアバリューギャップの可能性があります。フェアバリューギャップのアプローチで最も難しいのは、他のチャートパターンと同様、価格チャート上でこの特別な構成を見抜くことです。FVGは、特定のガイドラインに沿った3つのローソク足デザインの外観を要求します。この場合、1本目と3本目のローソク足の髭の間の面積または距離がフェアバリューギャップとなります。

チャート上でFVGを見つける方法は以下の通りです。

  • 大きなローソク足を見つける:フェアバリューギャップを把握しようとするとき、最初にすべきことは、価格チャートから大きなローソク足を探すことです。実体と髭の比率は70%程度が理想的です。
  • 隣接するローソク足を調べる:巨大なローソク足を見つけたら、その前後にあるローソク足を調べます。巨大なローソク足が、これらの近くのローソク足と完全に重なってはなりません。むしろ、巨大なローソク足の上下にわずかな重なりがあるかもしれません。フェアバリューギャップは、隣接するローソク足の芯と芯の間に生じます。
  • フェアバリューの差を特定する:フェアバリューギャップを定義し、最後のステップとして価格チャートに描かなければなりません。弱気トレンドにおける直前のローソク足の高値と安値の間の価格幅は、フェアバリューギャップと呼ばれます。市場の不均衡が顕在化し、取引チャンスとなる可能性があります。強気トレンドについても同様ですが、条件は正反対です。



  • エントリシグナルとエグジットシグナル:著しいローソク足の不均衡を伴うフェアバリューギャップを特定したら、トレーダーはそれに応じて取引を実行します。例えば、市場価格が推定フェアバリューより低く、強気の包み足パターンが出現した場合、トレーダーは価格の調整を予想してロングポジションを建てることを検討するかもしれません。逆に、市場価格がフェアバリューより高く、弱気の包み足パターンが現れた場合、トレーダーはショートポジションを建てるかもしれません。
  • リスク管理:トレーダーは、逆指値注文の設定やポジションサイジングなどのリスク管理手法を導入し、取引が予想通りに展開しなかった場合の潜在的な損失を軽減します。
  • モニタリングと調整:トレーダーは継続的に取引を監視し、市場の状況の変化やフェアバリューの見積もりの再評価に基づいてポジションを調整します。

すでにお気づきのように、フェアバリューギャップには2つの種類があり、それぞれにトレーダーにとってユニークな影響があります。詳しい説明は以下の通りです。

  • 弱気なフェアバリューギャップ(過小評価されたフェアバリューギャップ)

この種のFVGは、通貨ペアの価格、またはその他の金融資産の価格が、現在そのフェアバリューを下回っていることを示します。端的に言えば、トレーダーはこの非効率に対処するために、市場のリトレースメントを期待することができます。チャート上に大きな弱気ローソク足があれば、それはおそらくFVGが過小評価されていることを示しています。

  • 強気のフェアバリューギャップ(過大評価されたフェアバリューギャップ)

一方、過大評価されたFVGは、金融資産や通貨ペアが現在、フェアバリューより高く取引されていることを示します。市場は過熱しており、訂正は近くなります。相場が上昇する前に、トレーダーは相場が修正されるにつれて価格が戻ることを予期すべきです。


フェアバリューギャップ(FVG)取引戦略の設計図

フェアバリューギャップ戦略の定義と説明が終わったところで、次にFVG戦略を取引するために考慮すべき指定条件を段階的に定義してみましょう。2種類のFVGが市場に存在する可能性があることを思い出してください。

  • 強気のFVG: このストラテジーでは、大きな値動きのある強気ローソク足を見つけ、左右に隣接するローソク足を評価する必要があります。3本目のローソク足と1本目のローソク足の差を時系列的に求め、その差が事前定義されたポイント以内でなければ、強気のFVGと判断し、それに従って取引をおこないます。FVGが注目点となり、それをアルゴリズムに記録します。同時に、視覚化のために定義済みの長さで、チャート上に緑色/ライム色のFVGを描画します。これは、強気のFVG設定を見つけたこと、そしてその設定で取引できることを示します。そこで、価格が戻ってFVGの下限エリアに触れたら、即座に成行買い注文を出します。利食いはFVGの上部に設定され、損切りは注文の始値より下に設定され、リスクとリターンの比率は1:3です。ただし、あらかじめ設定された長さが過ぎても価格がFVGの設定に戻らなければ、それを関心から取り除くだけです。


他のすべての強気FVG設定についても同じプロセスが続きます。

  • 弱気なFVG: ここでも、大きな値動きのある弱気ローソク足を見つけ、その左右のローソク足を評価する必要があります。3本目のローソク足と1本目のローソク足の差を列的に求め、その差が事前定義されたポイント以内でなければ、弱気のFVGと判断し、それに従って取引をおこないます。FVGが注目点となり、それをアルゴリズムに記録します。同時に、視覚化のために定義済みの長さで、チャート上に赤色のFVG描画します。これは、弱気のFVG設定を見つけたこと、そしてその設定を取引できることを示します。そこで、価格が戻ってFVGの上限エリアに触れたら、即座に成行売り注文を出します。利食いはFVGの下部に設定され、損切りは注文の始値より上に設定され、リスクとリターンの比率は1:10です。ただし、あらかじめ設定された長さが過ぎても価格がFVGの設定に戻らなければ、それを関心から取り除くだけです。


他のすべての弱気FVG設定についても同じプロセスが続きます。


MQL5のフェアバリューギャップ(FVG)取引システム

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

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


ここで[新規作成]をクリックし、[エキスパートアドバイザ(テンプレート)]をチェックして、[次へ]をクリックします。


新しいファイルを開く

次に、希望するEAファイル名を入力し、[次へ]を、もう一度[次へ]をクリックし、[完了]をクリックします。以上の作業を終えれば、FVG戦略をプログラムする準備が整います。

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

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

ここでも#defineを使用して変数を定義し、作成されるすべてのFVGの長方形とその色の接頭辞として割り当てます。

#define FVG_Prefix "FVG REC "
#define CLR_UP clrLime
#define CLR_DOWN clrRed

以下で、前述のパラメータの重要性を図解します。


EAのコーディングをさらに簡単にするために、いくつかの変数を事前に定義しておく必要があります。これらは、不均衡バーが実行可能と見なされるための最小ポイントであり、描画された長方形の長さは、この場合、中央の不均衡バーからの延長バーです。これらをグローバル変数として定義します。

int minPts = 100;
int FVG_Rec_Ext_Bars = 10;

以下はそのイラストです。最小点をminPts、長方形の長さを表すバーの範囲をFVG_Rec_Barsと定義します。


最後に、string、integer、datetime、booleanデータ型の変数からなる 4 つの配列を定義し、EA の作成に使用するデータを格納します。これもグローバルです。

string totalFVGs[];
int barINDICES[];
datetime barTIMEs[];
bool signalFVGs[];

OnInitで、チャート上の表示されているバーのFVG設定を検索します。こうすることで、EAはより指標優位になります。ユーザーがチャートを開始しても、以前のFVG設定を見ることができ、その前のローソク足でさらに作成されるのを待つことができます。 

まず、チャートに表示されているすべてのバーを取得し、その数を知らせます。

   int visibleBars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   Print("Total visible bars on chart = ",visibleBars);

チャートに長方形オブジェクトがない場合は、新しいデータに対応するため、ストレージ配列を解放してゼロにリサイズします。EAを再度初期化する前に、ユーザーがチャート上のオブジェクトをすべて削除してしまう可能性があるため、これは非常に重要なステップです。

   if (ObjectsTotal(0,0,OBJ_RECTANGLE)==0){
      Print("No FVGs Found, Resizing storage arrays to 0 now!!!");
      ArrayResize(totalFVGs,0);
      ArrayResize(barINDICES,0);
      ArrayResize(signalFVGs,0);
   }

オブジェクトの重なりを避けるために、もう一度、以前のFVG設定をすべて取り除き、現在適用されているチャート環境プロパティに基づいて新しいものを作成します。これは、FVG設定の接頭辞を使用することで実現され、EAによって作成されたFVG長方形オブジェクトのみを削除することで、他のEAと互換性を持たせることができます。

   ObjectsDeleteAll(0,FVG_Prefix);

次に、チャート上のすべての可視バーをループし、バーのプロパティを取得し、有効なFVG設定であるかどうかを確認します。

以下、順を追って説明します。

強気のFVG設定を検索するには、インデックス i で最初のバーの安値を取得します(技術的には、設定がすでに3つのバーのみであると仮定した場合、これはバー番号0になります)。次に、インデックスi+2で3番目のバーの高値を取得し、それらのギャップ差をポイント形式で取得します。

      double low0 = iLow(_Symbol,_Period,i);
      double high2 = iHigh(_Symbol,_Period,i+2);
      double gap_L0_H2 = NormalizeDouble((low0 - high2)/_Point,_Digits);

弱気の設定を探すときも同じロジックが適用されます。弱気のFVG設定を検索するには、インデックスiで最初のバーの高値を取得します(すでに3つのバーのみの設定があると仮定した場合、技術的にはこれはバー番号0になります)。次に、インデック i+2で3 番目のバーの安値を取得し、それらのギャップ差をポイント形式で取得します。

      double high0 = iHigh(_Symbol,_Period,i);
      double low2 = iLow(_Symbol,_Period,i+2);
      double gap_H0_L2 = NormalizeDouble((low2 - high0)/_Point,_Digits);

ポイント差を取得したら、今度は返された値を使用して、FVGの設定が存在するかどうかを確認することができます。そのために、2つのブール変数を使用します。 

  1. FVG_UP:バーインデックスiの安値がバーインデックスi+2の高値より大きく、同時に計算されたギャップポイントが最小許容長方形ポイントより大きいことを確認

  2. FVG_DOWN:バーインデックスiの高値がバーインデックスi+2の安値より小さく、同時に計算されたギャップポイントが最小許容長方形ポイントより大きいことを確認

最小ポイントを確認することで、自然発生的な値動きによる有効で意味のあるFVG設定だけを確保し、チャートを荒らさないようにしています。

      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;

FVGの設定が確認できたら、次に、ストレージ配列にデータドキュメンテーションとともに、それぞれのFVGの設定を作成します。

datetime型の変数time1を定義し、インデックスi+1の中央のバー(中央または2番目のバー)の時間を格納します。ここで長方形が始まります。

ここでも、double型の変数price1を定義し、三項演算子を使用して、強気FVG設定の場合は3本目のバーの高値を、弱気FVG設定の場合は1本目のバーの高値を返します。

厳密には、これは描画される長方形オブジェクトの最初のポイントの座標となります。

         datetime time1 = iTime(_Symbol,_Period,i+1);
         double price1 = FVG_UP ? high2 : high0;

最初の座標を定義した後、2番目の座標を定義します。 

datetime型の変数time2を定義し、描画された長方形が終了するバーの時間を格納します。これは単純に、開始時刻であるtime1に、長方形を延長するバー数を加えることで実現できます。

ここでも、double型の変数price2を定義し、三項演算子を使用して、強気FVG設定の場合は最初のバーの安値を、弱気FVG設定の場合は3番目のバーの安値を返します。

         datetime time2 = time1 + PeriodSeconds(_Period)*FVG_Rec_Ext_Bars;
         double price2 = FVG_UP ? low0 : low2;

以下は、長方形をスムーズに作成するために必要な座標を可視化したものです。


FVG設定の座標を取得したら、そのFVG長方形オブジェクトに名前を付ける必要があります。

定義済みの接頭辞をFVGの接頭辞として使用し、それに作成時刻を加えます。これにより、第1に、一度そのバーの時間でFVGオブジェクトを作成すると、その特定のローソク足の時間では同じ名前のオブジェクトがすでに存在しているため、再度作成することができないようにし、第2に、同じようなバーの時間は他に存在しないため、一意性があることを保証するものです。

         string fvgNAME = FVG_Prefix+"("+TimeToString(time1)+")";

また、強気と弱気を区別できるように、FVG設定に異なる色を割り当てる必要があります。強気の設定には、あらかじめ定義された強気の色、すなわちCLR_UPが割り当てられ、弱気の設定には、あらかじめ定義された弱気の色、すなわちCLR_DOWNが割り当てられます。

         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;

ここまでで、それぞれのFVG設定をチャートに描くのに必要なものはすべて揃いました。 

これを簡単におこなうには、グローバルスコープにvoid関数を作成します。この関数をCreateRecと定義し、オブジェクト名、ポイント1とポイント2それぞれの座標、色、FVG設定の作成に必要な変数を渡します。

void CreateRec(string objName,datetime time1,double price1,
               datetime time2, double price2,color clr){
   if (ObjectFind(0,objName) < 0){
      ObjectCreate(0,objName,OBJ_RECTANGLE,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_FILL,true);
      ObjectSetInteger(0,objName,OBJPROP_BACK,false);
      
      ChartRedraw(0);
   }
}

作成された関数を後で使用し、事前に作成されたパラメータを渡して、チャート上にそれぞれのFVG長方形を作成します。

         CreateRec(fvgNAME,time1,price1,time2,price2,fvgClr);

FVG設定を作成したら、データをストレージ配列に保存する必要があります。ただし、動的配列を作成したので、まずArrayResize関数を使用して、さらにデータ値を取り込むために配列のサイズを変更します。

         ArrayResize(totalFVGs,ArraySize(totalFVGs)+1);
         ArrayResize(barINDICES,ArraySize(barINDICES)+1);

データストレージ配列のサイズを変更したら、新しいデータをそれぞれ配列に追加します。

         totalFVGs[ArraySize(totalFVGs)-1] = fvgNAME;
         barINDICES[ArraySize(barINDICES)-1] = i+1;

ここまでで、チャート上のすべての可視バーのFVG設定の作成に成功しました。このマイルストーンの結果は以下の通りです。


ユーザーは、FVGの設定がすでに成功しているという意味で、確認できたはずの設定をいったん短くすることを要求しているのかもしれません。よって、価格がそれを確認した時点で、設定の余分な長さを切り捨てる必要があります。これは、作成され確認されたすべてのFVG設定をループし、それぞれの詳細を取得し、制御ロジックに対してデータを確認し、最後に設定をそれぞれ更新することで実現します。

ここでは、その方法を順を追って説明します。

作成されたすべてのFVG設定をforループで使用してループします。

   for (int i=ArraySize(totalFVGs)-1; i>=0; i--){// ... }

設定データを取得します。

      string objName = totalFVGs[i];
      string fvgNAME = ObjectGetString(0,objName,OBJPROP_NAME);
      int barIndex = barINDICES[i];
      datetime timeSTART = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,0);
      datetime timeEND = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,1);
      double fvgLOW = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,0);
      double fvgHIGH = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,1);
      color fvgColor = (color)ObjectGetInteger(0,fvgNAME,OBJPROP_COLOR);

すべての長方形延長バーをループします(すなわち選択された長方形の長さをループ)。

      for (int k=barIndex-1; k>=(barIndex-FVG_Rec_Ext_Bars); k--){//... }

バーのデータを取得します。

         datetime barTime = iTime(_Symbol,_Period,k);
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);

長方形が時系列で最初に表示されるバーを超えている場合は、単に最初のバーの時間、すなわちインデックス0のバー、または簡単に言えば現在のバーで切り捨てます。オーバーフロー検出を通知し、FVG設定を現在のバーの時間に更新し、ループを終了します。

         if (k==0){
            Print("OverFlow Detected @ fvg ",fvgNAME);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }

設定を簡単に更新するために、void関数UpdateRecを作成し、オブジェクト名と、更新される長方形オブジェクトの2つの重要なポイントの座標を渡します。

void UpdateRec(string objName,datetime time1,double price1,
               datetime time2, double price2){
   if (ObjectFind(0,objName) >= 0){
      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);
      
      ChartRedraw(0);
   }
}

FVGの設定が弱気FVGであり、色によって検出され、選択されたバーの高値がFVGオブジェクトの上限座標より上にある場合、FVGが成功したことを通知し、それに応じてFVGを切り捨て、FVGのブレイクが発生したローソク足に2つ目のポイントの座標を更新します。強気のFVG設定も同様です。

         if ((fvgColor == CLR_DOWN && barHigh > fvgHIGH) ||
            (fvgColor == CLR_UP && barLow < fvgLOW)
         ){
            Print("Cut Off @ bar no: ",k," of Time: ",barTime);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }


すべての準備が整ったら、ストレージ配列のサイズをゼロに変更し、OnTickの準備をします。

   ArrayResize(totalFVGs,0);
   ArrayResize(barINDICES,0);

チャート上の可視バー上にFVG設定を作成し、それに応じて更新するOnInitの全コードは以下のようになります。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   int visibleBars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   Print("Total visible bars on chart = ",visibleBars);
   
   if (ObjectsTotal(0,0,OBJ_RECTANGLE)==0){
      Print("No FVGs Found, Resizing storage arrays to 0 now!!!");
      ArrayResize(totalFVGs,0);
      ArrayResize(barINDICES,0);
      ArrayResize(signalFVGs,0);
   }
   
   ObjectsDeleteAll(0,FVG_Prefix);
   
   for (int i=0; i<=visibleBars; i++){
      //Print("Bar Index = ",i);
      double low0 = iLow(_Symbol,_Period,i);
      double high2 = iHigh(_Symbol,_Period,i+2);
      double gap_L0_H2 = NormalizeDouble((low0 - high2)/_Point,_Digits);
      
      double high0 = iHigh(_Symbol,_Period,i);
      double low2 = iLow(_Symbol,_Period,i+2);
      double gap_H0_L2 = NormalizeDouble((low2 - high0)/_Point,_Digits);
      
      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;
      
      if (FVG_UP || FVG_DOWN){
         Print("Bar Index with FVG = ",i+1);
         datetime time1 = iTime(_Symbol,_Period,i+1);
         double price1 = FVG_UP ? high2 : high0;
         datetime time2 = time1 + PeriodSeconds(_Period)*FVG_Rec_Ext_Bars;
         double price2 = FVG_UP ? low0 : low2;
         string fvgNAME = FVG_Prefix+"("+TimeToString(time1)+")";
         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;
         CreateRec(fvgNAME,time1,price1,time2,price2,fvgClr);
         Print("Old ArraySize = ",ArraySize(totalFVGs));
         ArrayResize(totalFVGs,ArraySize(totalFVGs)+1);
         ArrayResize(barINDICES,ArraySize(barINDICES)+1);
         Print("New ArraySize = ",ArraySize(totalFVGs));
         totalFVGs[ArraySize(totalFVGs)-1] = fvgNAME;
         barINDICES[ArraySize(barINDICES)-1] = i+1;
         ArrayPrint(totalFVGs);
         ArrayPrint(barINDICES);
      }
   }
   
   for (int i=ArraySize(totalFVGs)-1; i>=0; i--){
      string objName = totalFVGs[i];
      string fvgNAME = ObjectGetString(0,objName,OBJPROP_NAME);
      int barIndex = barINDICES[i];
      datetime timeSTART = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,0);
      datetime timeEND = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,1);
      double fvgLOW = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,0);
      double fvgHIGH = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,1);
      color fvgColor = (color)ObjectGetInteger(0,fvgNAME,OBJPROP_COLOR);
      
      Print("FVG NAME = ",fvgNAME," >No: ",barIndex," TS: ",timeSTART," TE: ",
            timeEND," LOW: ",fvgLOW," HIGH: ",fvgHIGH," CLR = ",fvgColor);
      for (int k=barIndex-1; k>=(barIndex-FVG_Rec_Ext_Bars); k--){
         datetime barTime = iTime(_Symbol,_Period,k);
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);
         //Print("Bar No: ",k," >Time: ",barTime," >H: ",barHigh," >L: ",barLow);
         
         if (k==0){
            Print("OverFlow Detected @ fvg ",fvgNAME);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }
         
         if ((fvgColor == CLR_DOWN && barHigh > fvgHIGH) ||
            (fvgColor == CLR_UP && barLow < fvgLOW)
         ){
            Print("Cut Off @ bar no: ",k," of Time: ",barTime);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }
      }
      
   }
   
   ArrayResize(totalFVGs,0);
   ArrayResize(barINDICES,0);

   return(INIT_SUCCEEDED);
}

OnTickでは、同様の関数とロジックが使用されます。事前に定義されたすべての延長バー、つまり現在のバーからFVGの設定の長さをループし、潜在的な設定を検索し、何か見つかって確認されたら作成します。 

以下は、使用されている作成ループの若干の違いです。

OnInitのFVG設定作成ループでは、チャート上のすべての可視バーを考慮しますが、ここでは逆に、最後に定義された延長の長さだけを考慮します。

   for (int i=0; i<=FVG_Rec_Ext_Bars; i++){//... }

バーの情報を取得するときは、バーがまだ形成過程にあるため、現在のバーインデックスより前のバーから開始します。そこで、選択されたインデックスに1を加えます。

      double low0 = iLow(_Symbol,_Period,i+1);
      double high2 = iHigh(_Symbol,_Period,i+2+1);

OnInitのFVG切り捨てループで、確認済みとテスト済みのFVG設定を切り捨てます。OnTickでは、設定を表示したいので、切り捨てません。その代わり、即座に成行注文を出します。繰り返しますが、価格がバーの長さを超えると、その設定の取引は一切できなくなるので、配列に保存されたデータを削除します。以下はコードの違いです。

新しいブール変数fvgExistを追加し、それをfalseに初期化することで、スキャンされたバー内の設定の有無に関するフラグを保持できるようにします。

      bool fvgExist = false;

再度、現在のバーの1つ前のバー、つまり0 + 1 = 1から始まる定義済みのバーをループし、選択したバーの高値と安値を取得します。いずれかの価格が設定の2番目のポイントの座標と一致すれば、それはまだ範囲内にあってまだ取引可能であるため、fvgExist変数をtrueに設定します。

      for (int k=1; k<=FVG_Rec_Ext_Bars; k++){
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);
         
         if (barHigh == fvgLow || barLow == fvgLow){
            //Print("Found: ",fvgNAME," @ bar ",k);
            fvgExist = true;
            break;
         }
      }

市場に潜入したいのであり、そのためにはエントリ価格水準が必要なので、現在の銘柄の相場を早めに定義します。

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

その後、if文を使用して、選択したFVG設定が弱気設定であるかどうか、現在のBid価格が設定の上限座標を上回っているかどうか、同時にこれが設定の最初の取引シグナルであるかどうかを確認し、すべての条件が満たされたら、取引量0.01で売り注文を出します。建値は現在の市場の買値、利食いはFVGの下限座標に設定され、損切りはエントリ価格より上に設定されます。リスクとリターンの比率は1:10です。ポジションが開始されたら、その特定のインデックスのシグナルデータをtrueに設定し、次のティックでその特定の設定に基づく他のポジションを建てないようにします。

      if (fvgColor == CLR_DOWN && Bid > fvgHigh && !signalFVGs[j]){
         Print("SELL SIGNAL For (",fvgNAME,") Now @ ",Bid);
         double SL_sell = Ask + NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);
         
         if (Check2_Margin(ORDER_TYPE_SELL,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_SELL,SL_sell,fvgLow)){
            obj_Trade.Sell(trade_lots,_Symbol,Bid,SL_sell,fvgLow);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }


強気についての確認は同様ですが、条件が反対です。

      else if (fvgColor == CLR_UP && Ask < fvgLow && !signalFVGs[j]){
         Print("BUY SIGNAL For (",fvgNAME,") Now @ ",Ask);
         double SL_buy = Bid - NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);

         if (Check2_Margin(ORDER_TYPE_BUY,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_BUY,SL_buy,fvgHigh)){
            obj_Trade.Buy(trade_lots,_Symbol,Ask,SL_buy,fvgHigh);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }


最後に、FVGの設定が存在しなくなれば、取引ができなくなるので、関心がなくなります。対応するデータを取り除くことで、ストレージ配列を解放します。これにはArrayRemove関数を使用します。現在選択されている単一のFVG設定のデータのみを削除するため、この関数に、配列、開始位置(ここでは最初のデータの場合は0)、合計要素(ここでは1)を渡します。

      if (fvgExist == false){
         bool removeName = ArrayRemove(totalFVGs,0,1);
         bool removeTime = ArrayRemove(barTIMEs,0,1);
         bool removeSignal = ArrayRemove(signalFVGs,0,1);
         if (removeName && removeTime && removeSignal){
            Print("Success removing the FVG DATA from the arrays. New Data as Below:");
            Print("FVGs: ",ArraySize(totalFVGs)," TIMEs: ",ArraySize(barTIMEs),
                     " SIGNALs: ",ArraySize(signalFVGs));
            ArrayPrint(totalFVGs);
            ArrayPrint(barTIMEs);
            ArrayPrint(signalFVGs);
         }
      }


以下は、FVG設定を作成し、確定した設定を取引し、ストレージ配列から制限外のデータを削除するために必要なOnTickコードです。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   
   for (int i=0; i<=FVG_Rec_Ext_Bars; i++){
      double low0 = iLow(_Symbol,_Period,i+1);
      double high2 = iHigh(_Symbol,_Period,i+2+1);
      double gap_L0_H2 = NormalizeDouble((low0 - high2)/_Point,_Digits);
      
      double high0 = iHigh(_Symbol,_Period,i+1);
      double low2 = iLow(_Symbol,_Period,i+2+1);
      double gap_H0_L2 = NormalizeDouble((low2 - high0)/_Point,_Digits);
      
      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;
      
      if (FVG_UP || FVG_DOWN){
         datetime time1 = iTime(_Symbol,_Period,i+1+1);
         double price1 = FVG_UP ? high2 : high0;
         datetime time2 = time1 + PeriodSeconds(_Period)*FVG_Rec_Ext_Bars;
         double price2 = FVG_UP ? low0 : low2;
         string fvgNAME = FVG_Prefix+"("+TimeToString(time1)+")";
         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;
         
         if (ObjectFind(0,fvgNAME) < 0){
            CreateRec(fvgNAME,time1,price1,time2,price2,fvgClr);
            Print("Old ArraySize = ",ArraySize(totalFVGs));
            ArrayResize(totalFVGs,ArraySize(totalFVGs)+1);
            ArrayResize(barTIMEs,ArraySize(barTIMEs)+1);
            ArrayResize(signalFVGs,ArraySize(signalFVGs)+1);
            Print("New ArraySize = ",ArraySize(totalFVGs));
            totalFVGs[ArraySize(totalFVGs)-1] = fvgNAME;
            barTIMEs[ArraySize(barTIMEs)-1] = time1;
            signalFVGs[ArraySize(signalFVGs)-1] = false;
            ArrayPrint(totalFVGs);
            ArrayPrint(barTIMEs);
            ArrayPrint(signalFVGs);
         }
      }
   }
   
   for (int j=ArraySize(totalFVGs)-1; j>=0; j--){
      bool fvgExist = false;
      string objName = totalFVGs[j];
      string fvgNAME = ObjectGetString(0,objName,OBJPROP_NAME);
      double fvgLow = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,0);
      double fvgHigh = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,1);
      color fvgColor = (color)ObjectGetInteger(0,fvgNAME,OBJPROP_COLOR);
      
      for (int k=1; k<=FVG_Rec_Ext_Bars; k++){
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);
         
         if (barHigh == fvgLow || barLow == fvgLow){
            //Print("Found: ",fvgNAME," @ bar ",k);
            fvgExist = true;
            break;
         }
      }
      
      //Print("Existence of ",fvgNAME," = ",fvgExist);
      
      double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      
      if (fvgColor == CLR_DOWN && Bid > fvgHigh && !signalFVGs[j]){
         Print("SELL SIGNAL For (",fvgNAME,") Now @ ",Bid);
         double SL_sell = Ask + NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);
         
         if (Check2_Margin(ORDER_TYPE_SELL,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_SELL,SL_sell,fvgLow)){
            obj_Trade.Sell(trade_lots,_Symbol,Bid,SL_sell,fvgLow);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }
      else if (fvgColor == CLR_UP && Ask < fvgLow && !signalFVGs[j]){
         Print("BUY SIGNAL For (",fvgNAME,") Now @ ",Ask);
         double SL_buy = Bid - NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);

         if (Check2_Margin(ORDER_TYPE_BUY,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_BUY,SL_buy,fvgHigh)){
            obj_Trade.Buy(trade_lots,_Symbol,Ask,SL_buy,fvgHigh);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }
      
      if (fvgExist == false){
         bool removeName = ArrayRemove(totalFVGs,0,1);
         bool removeTime = ArrayRemove(barTIMEs,0,1);
         bool removeSignal = ArrayRemove(signalFVGs,0,1);
         if (removeName && removeTime && removeSignal){
            Print("Success removing the FVG DATA from the arrays. New Data as Below:");
            Print("FVGs: ",ArraySize(totalFVGs)," TIMEs: ",ArraySize(barTIMEs),
                     " SIGNALs: ",ArraySize(signalFVGs));
            ArrayPrint(totalFVGs);
            ArrayPrint(barTIMEs);
            ArrayPrint(signalFVGs);
         }
      }      
   }
   
}

お疲れ様です。FVG/不均衡戦略に基づいて売買シグナルを生成する、スマートマネーコンセプト取引システムを作成しました。


フェアバリューギャップ(FVG)のストラテジーテスター結果

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

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


  • バックテスト結果



結論

結論として、説明したようなさまざまなトピックを通じてフェアバリューギャップまたは不均衡戦略をコード化することを探求することは、定量的取引手法に貴重な洞察をもたらします。この記事では、データ分析、統計モデリング、アルゴリズム取引技術などの主要な要素に触れながら、このような戦略を実行するための複雑な詳細を掘り下げてきました。

まず、フェアバリューギャップや不均衡の概念を理解することは、戦略の根幹を成すものとして不可欠です。これは、資産の市場価格とその本質的価値との間の不一致を特定し、統計的手法を利用してこれらの差異を正確に測定することです。

さらに、この記事では、金融データから意味のある洞察を引き出すための堅牢なデータ分析技術の重要性を強調しています。時系列分析、ローソク足分析、センチメント分析などの手法は、取引の意思決定に役立つパターンやトレンドを特定する上で重要な役割を果たします。

さらに、戦略のコーディング面では、MQL5のライブラリや関数に精通するだけでなく、MQL5のプログラミング言語に習熟することの重要性が強調されています。効率的なコーディングの実践は、取引プロセスの自動化を可能にし、より迅速な執行と戦略の拡張性を促進します。

免責条項: この記事で説明されている情報は、あくまでも教育目的です。これは、スマートマネーコンセプトのアプローチに基づくフェアバリューギャップEAの作成方法に関する洞察を示すことを意図しています。より多くの最適化とデータ抽出を考慮に入れたより良いEAを作成するためのベースとして使用してください。提示された情報は、いかなる取引結果も保証するものではありません。 

全体として、この記事は、ダイナミックな金融市場をナビゲートするための効果的な戦略を開発するための定量取引、統計、バー分析の学際的な性質を強調しています。理論的なコンセプトと実践的なコーディングの実装を統合することで、読者はクオンツ取引、特にスマートマネーコンセプト(SMC)のアプローチを成功させるために必要なツールを身につけることができます。


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

添付されたファイル |
FVG_SMC_EA.mq5 (21.23 KB)
データサイエンスと機械学習(第22回):オートエンコーダニューラルネットワークを活用してノイズからシグナルへと移行することで、よりスマートな取引を実現する データサイエンスと機械学習(第22回):オートエンコーダニューラルネットワークを活用してノイズからシグナルへと移行することで、よりスマートな取引を実現する
目まぐるしく変化する金融市場の世界では、意味のあるシグナルをノイズから切り離すことが、取引を成功させるために極めて重要です。オートエンコーダは、洗練されたニューラルネットワークアーキテクチャを採用するため、市場データ内の隠れたパターンを発見し、ノイズの多い入力を実用的な洞察に変換することに優れています。この記事では、オートエンコーダがいかに取引慣行に革命をもたらし、トレーダーに意思決定を強化し、今日のダイナミックな市場で競争力を得るための強力なツールを提供しているかを探ります。
知っておくべきMQL5ウィザードのテクニック(第15回):ニュートンの多項式を用いたサポートベクトルマシン 知っておくべきMQL5ウィザードのテクニック(第15回):ニュートンの多項式を用いたサポートベクトルマシン
サポートベクトルマシンは、データの次元を増やす効果を調べることで、あらかじめ定義されたクラスに基づいてデータを分類します。これは教師あり学習法で、多次元のデータを扱う可能性を考えるとかなり複雑です。この記事では、2次元データの非常に基本的な実装であるニュートンの多項式が、価格とアクションを分類する際にどのように効率的に実行できるかを検討します。
ONNX統合の課題を克服する ONNX統合の課題を克服する
ONNXは、異なるプラットフォーム間で複雑なAIコードを統合するための素晴らしいツールです。ただし、この素晴らしいツールを最大限に活用するためにはいくつかの課題に対処する必要があります。この記事では、読者が直面する可能性のある一般的な問題と、それを軽減する方法について説明します。
ニュース取引が簡単に(第1回):データベースの作成 ニュース取引が簡単に(第1回):データベースの作成
ニュース取引は複雑で圧倒されるかもしれませんが、この記事ではニュースデータを入手する手順を説明し、さらに、MQL5経済指標カレンダーとその特徴についても学びます。