English Deutsch
preview
市場力学をマスターする:支持&抵抗戦略エキスパートアドバイザー(EA)の作成

市場力学をマスターする:支持&抵抗戦略エキスパートアドバイザー(EA)の作成

MetaTrader 5トレーディング | 16 8月 2024, 07:46
39 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

この記事では、純粋なプライスアクション取引の文脈で、支持抵抗のFX取引戦略と、それに基づくエキスパートアドバイザー(EA)の作成について説明します。MetaTrader 5 (MT5)用のMetaQuotes言語5(MQL5)で、ストラテジーの定義、種類、説明、開発について探っていきます。戦略の背景にある理論だけでなく、それぞれの概念、分析、識別、チャート上での可視化など、トレーダーが相場の動きを予測する能力を高め、より的確な判断を下し、最終的にはリスク管理に熟達するために学ぶべき有用なツールであることを説明します。以下の題材を使用して、上記のことを達成しましょう。

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

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


支持と抵抗の定義

支持と抵抗のFX取引戦略は、多くのFXトレーダーが、市場が一時停止または反転する可能性が高い価格水準を分析し、特定するために使用するファンダメンタル分析ツールです。厳密にいえば、これらの水準は過去の価格によって拒否される傾向があり、価格が一旦この水準で一時停止したり反転したりするため、長期的に重要な水準となります。これらのレベルが構築される場所では、通常、価格が主要レベルから複数回跳ね返り、強い買いまたは売りの関心を示します。 


支持と抵抗の説明

支持&抵抗戦略の説明は、取引シナリオへの応用を中心に展開されます。支持線は通常、価格が突破しようともがく下限を示し、需要の集中を示唆し、抵抗線は供給の集中を示す上限を示します。買い手は通常支持レベルで市場に参入し、価格が上昇する可能性が高いため、トレーダーにとっては買い(ロング)を検討するのに適した時期です。一方、抵抗レベルでは売り手が参入し、価格が下落する可能性があり、トレーダーは売り(ショート)をおこなうことができます。ここで、私たちが言いたいことを可視化してみましょう。

S & R

市場参入は常にダイナミックで、その人の趣味や嗜好に左右されますが、レベルの取引には2つの基本的な方法があります。一部のトレーダーは、価格が支持レベルに向かって下落したときに買い、価格が抵抗レベルに向かって上昇したときに売るというバウンス取引を好みます。逆に、価格が抵抗レベルをブレイクアップしたときに買い、価格がサポートレベルをブレイクダウンしたときに売ることで、ブレイクを取引することを好むトレーダーもいます。したがって、ブレイクをフェードすることも、ブレイクを取引することもできます。


支持と抵抗の種類

支持レベルと抵抗レベルには4種類あります。

  • 丸数字の支持レベルと抵抗レベル:これらの水準は、価格が同じ価格の水準から跳ね返されることによって形成され、水平価格チャネルにつながります。例えば、ある市場のスイング安値は、0.65432、0.65435、0.65437と同じレベルになる可能性があります。通常、これらは同じレベルで、偏角はごくわずかで、価格需要の集中を示しています。
  • トレンドラインチャネルの支持レベルと需要レベル:上昇トレンドラインや下降トレンドラインによって形成されるスイングポイントは、価格が反応しやすい需給ゾーンを形成します。

TRENDLINE S&R

  • フィボナッチの支持レベルと抵抗レベル:フィボナッチは、トレーダーが価格の反転ゾーンを特定するために使用され、これらのゾーンは、支持レベルと抵抗レベルの需給ゾーンとして機能する傾向があります。
  • 指標の支持レベルと抵抗レベル:移動平均などのテクニカル指標は、価格が反応しやすいゾーンを示し、支持レベルと抵抗レベルのピボットを形成します。

MA IND S&R


取引戦略の説明

これまで見てきたように、FXの領域にはさまざまなタイプの支持&抵抗戦略があります。この記事では、水平方向の丸め数字タイプを選択して操作しますが、同じ概念を他のタイプにも適用して適応させることができます。

まず、チャートを分析し、支持と抵抗の座標を取得します。それぞれの座標が特定されたら、そのレベルをチャートに描きます。繰り返しになりますが、すべてのトレーダーは、フェードするかブレイクするかという2つの選択肢を持っています。私たちの場合は、ブレイクをフェードさせます。買いポジションは支持レベルをブレイクしたときに、売りポジションは抵抗レベルをブレイクしたときに建てます。簡単なことです。


取引戦略の設計図

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

  • 抵抗レベル

抵抗設計図

  • 支持レベル

支持設計図


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

支持&抵抗取引戦略に関するすべての理論を学んだら、その理論を自動化して、MetaQuote言語5 (MQL5)でMetaTrader 5 (MT5)用のEAを作成しましょう。

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

METAEDITORを開く

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

NEW EA

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

MQL WIZARD

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

EA名

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

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

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

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

CTRADEクラス

グローバルスコープでは、最高値と最安値のデータを格納する配列を定義する必要があります。これを後で操作して分析し、支持レベルと抵抗レベルを見つけます。レベルが見つかったら、それを配列に格納する必要があります。

double pricesHighest[], pricesLowest[];

double resistanceLevels[2], supportLevels[2];

ここでは、規定されたデータ量の最高値と最安値を保持する2つのdouble配列と、識別されて並び替えられた支持レベルと抵抗レベルのデータを保持する2つの追加配列を宣言します。これは通常、各レベルの2つの座標です。価格変数は空であるため、事前定義されたサイズのない動的配列になり、提供されたデータに基づいて任意の数の要素を保持できることに注意してください。逆に、レベル変数は2つの固定サイズを持っているため、静的配列となり、それぞれ正確に2つの要素を保持することができます。より多くの座標を使用する場合は、適切と思われるポイント数までサイズを大きくすることができます。

最後に、レベルを特定したら、可視化のためにチャートにプロットする必要があります。したがって、同じ取引口座に複数のEAがある場合に備えて、識別と一意性を容易にするために、行の名前、それぞれの割り当てられた色、それぞれの接頭辞を定義しなければなりません。これにより、EAは他のEAと互換性を持つことができ、EAはそのレベルを識別し、効果的かつ独自に作業することができます。

#define resLine "RESISTANCE LEVEL"
#define colorRes clrRed
#define resline_prefix "R"

#define supLine "SUPPORT LEVEL"
#define colorSup clrBlue
#define supline_prefix "S"

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

同様に、抵抗レベルの色を赤と定義し、最後に抵抗レベルを表す接頭辞「R」を定義します。これを使用して、チャート上の抵抗線にラベルを付けて識別します。抵抗レベルと同様に、同じ基準に従って支持レベルも定義します。

EAを初期化したら、データを時系列に設定する必要があるので、まず最新のデータを扱い、データを格納するストレージ配列を準備します。これはOnInitイベントハンドラでおこなわれます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   ArraySetAsSeries(pricesHighest,true);
   ArraySetAsSeries(pricesLowest,true);
   // define the size of the arrays
   ArrayResize(pricesHighest,50);
   ArrayResize(pricesLowest,50);
//---
   return(INIT_SUCCEEDED);
}

2つの異なることが起こります。まず、ArraySetAsSeries MQL5組み込み関数を使用して、価格ストレージ配列を時系列として設定します。この関数は、ターゲット配列とブールフラグ(この場合は変換を受け入れるためにtrue)の2つの引数を取ります。 つまり、配列は最も古いデータを最も大きいインデックス、最も新しいデータをインデックス0としてインデックスされます。ここに例があります。2020年から2024年までのデータを検索するとします。以下は、データを受け取る際の形式です。

1年 データ
2020 0
2021 1
2022 2
2023 3
2024 4

上記の形式のデータは、時系列に並べられ、最も古いデータが最初のインデックスにされるため、使い勝手が悪くなります。最新のデータから分析した方が便利なので、時系列を逆に並べると以下のようになります。

1年 データ
2024 4
2023 3
2022 2
2021 1
2020 0

上記の形式をプログラムで実現するには、先に説明したように、ArraySetAsSeries関数を利用します。次に、ArrayResize関数を使用して配列のサイズを定義し、それぞれ50個の要素を保持するように指定します。これは何でもよくて、私たちが取った任意の値なので、ご自由に無視してください。しかし、形式上、受信した価格データを最初の10個の重要なデータだけに並び替える予定なので、配列にあまり多くのデータは必要なく、余分なサイズは予約されます。配列のサイズを大きくしても意味がないことがわかるでしょう。

OnDeinitイベントハンドラでは、使用中のストレージデータをコンピュータのメモリから取り除く必要があります。これは資源の節約につながります。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   //ArrayFree(resistanceLevels); // cannot be used for static allocated array
   //ArrayFree(supportLevels); // cannot be used for static allocated array
   
   ArrayRemove(resistanceLevels,0,WHOLE_ARRAY);
   ArrayRemove(supportLevels,0,WHOLE_ARRAY);
}

EAが使用されなくなり、データが無駄になるので、ArrayFrE関数を使用してコンピュータのメモリを占有しているデータを処分します。この関数はvoidデータ型であり、引数(動的配列)を1つだけ受け取り、そのバッファを解放し、0次元のサイズを0に設定します。しかし、支持と抵抗の価格を格納する静的配列では、この関数は使用できません。だからといって、データを処分できないわけではありません。別の関数ArrayRemoveを呼び出し、廃棄したいデータを削除します。この関数は、3つの引数をとり、配列から指定された数の要素を削除するブーリアンデータ型です。対象となる配列変数を指定し、削除処理を開始するインデックスを指定します(この場合、すべてを削除したいのでゼロ)。最後に削除する要素数を指定します(この場合、全てを削除したいので配列全体)。

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

void OnTick(){
//---

}

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

OnTick関数が価格相場の変化ごとに生成されることがわかったので、少なくとも不必要なコードの実行を避け、デバイスのメモリを節約するために、ティックごとではなく、バーごとに1回コードを実行できるような制御ロジックを定義する必要があります。これは、支持レベルと抵抗レベルを探すときに必要なことです。各ティックでレベルを検索する必要はなく、同じローソク足であれば常に同じ結果が得られます。ロジックは次の通りです。

   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;

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

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

最後に、条件文を使用して、現在のバー数と前回のバー数が等しいかどうかを確認します。両者が等しい場合は、新しいバーが形成されていないことを意味するので、それ以降の実行を終了して戻ります。現在のバーカウントと前のバーカウントが等しくなければ、新しいバーが形成されたことを示します。この場合、prevBars変数をcurrBarsに更新し、次のティックでは、prevBars変数がチャート上のバーの数と同じになるようにします。

分析対象となるバーは、チャート上に表示されているバーのみです。これは、1,000万本のバーのような最も古いデータを考慮する必要がないからです。これは役に立たないからです。前年までさかのぼる支持レベルがある場合を想像してみてください。意味がないですね。チャート上に表示されるバーのみを検討します。現在の市場状況に対する最新の有効なデータであるからです。そのためには、以下のロジックを使用します。

   int visible_bars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);

visible_barsという整数変数を宣言し、ChartGetInteger関数を使用してチャート上の可視バーの合計を取得します。これはlongデータ型の関数なので、関数の前に(int)を付けて整数に型キャストします。もちろん、目標変数をlongとして定義することもできますが、そのような大きなメモリバイトを割り当てる必要はありません。 

レベルを検索するには、各バーをループする必要があります。そのためにはforループが不可欠です。

   for (int i=1; i<=visible_bars-1; i++){
   ...
   
   }

ループの開始点を指定するために、まずループカウンタの整数変数iを1に初期化します。1つは、現在のバーが形成段階にあり、したがってまだ未決定であるため、現在のバーの前のバーから開始することを意味します。それはどんな結果にもなり得ます。以下は、ループが実行され続けるためにtrueでなければならない条件です。「i」が、考慮されているバーの合計から1を引いた値以下である限り、ループは実行され続けます。最後に、ループが実行されるたびに、ループカウンタ「i」を1ずつインクリメントします。単純に、「i++」は「i=i+1」と同じです。デクリメントループを使用することもできます。その場合はループカウンタ演算子が「--」となり、現在のバーから最も古い最後のバーから分析が開始されますが、ここではインクリメントループを使用し、最新のバーから最も古いバーまで分析が開始されるようにします。

各ループで、バーまたはローソク足を選択するので、バーのプロパティを取得する必要があります。この場合、バーの始値、高値、安値、終値、および時間のプロパティだけが重要です。

      double open = iOpen(_Symbol,_Period,i);
      double high = iHigh(_Symbol,_Period,i);
      double low = iLow(_Symbol,_Period,i);
      double close = iClose(_Symbol,_Period,i);
      datetime time = iTime(_Symbol,_Period,i);

ここでは、それぞれのデータ型変数を宣言し、対応するデータに初期化します。例えば、iOpen関数を使用して、金融商品の銘柄名、期間、対象バーのインデックスを指定し、バーの始値を取得します。

バーのデータを取得した後、そのデータを直前の残りのバーと照合して、選択したバーと同じデータを持つバーを見つける必要があります。しかし、支持レベルや抵抗レベルが連続する2つのレベルでは意味がありません。少なくともレベルは離れているはずです。まず、これを定義してみましょう。

      int diff_i_j = 10;

現在のバーと、同じレベルの一致を確認するために考慮されるべきバーの差を保持する整数変数を定義します。ここでは10です。視覚的に表現すると、こういうことです。

BAR DIFFERENCE

これで、ロジックを組み込んだループを開始できます。

      for (int j=i+diff_i_j; j<=visible_bars-1; j++){
      ...
      
      }

内側のforループでは、現在のバーから10本目のバーから始まり、最後から2本目のバーまでのカウンタ整数変数jを使用します。これを簡単に理解し、結果に納得してから先に進むために、出力を出力して可視化してみましょう。

//Print in the outer loop
      Print(":: BAR NO: ",i);

      //Print in the inner loop
         Print("BAR CHECK NO: ",j);

選択ループバー

例えば、インデックス15で選択されたバーに対して、現在選択されているバーから10バー先のループを初期化していることがわかります。数学的には15+10=25となります。そして25バー目から、最後から2番目のバー(この例では33)までループを実行します。

必要に応じて棒グラフの間隔を正しく選択できるようになったので、選択した棒グラフのプロパティも取得できます。

         double open_j = iOpen(_Symbol,_Period,j);
         double high_j = iHigh(_Symbol,_Period,j);
         double low_j = iLow(_Symbol,_Period,j);
         double close_j = iClose(_Symbol,_Period,j);
         datetime time_j = iTime(_Symbol,_Period,j);

外側ループのプロパティ検索と同じロジックを考えます。唯一の違いは、プロパティが内側のforループ用であることを示すために、「_j」を追加して変数を定義していることと、バーの目標インデックスが「j」に変わることです。

これで必要な価格データがすべて揃ったので、支持レベルと抵抗レベルの確認に進むことができます。

         // CHECK FOR RESISTANCE
         double high_diff = NormalizeDouble((MathAbs(high-high_j)/_Point),0);
         bool is_resistance = high_diff <= 10;
         
         // CHECK FOR SUPPORT
         double low_diff = NormalizeDouble((MathAbs(low-low_j)/_Point),0);
         bool is_support = low_diff <= 10;

抵抗レベルを確認するために、double変数high_diffを定義し、外側のループで現在選択されているバーの高値と内側のループで現在選択されているバーの高値の差のデータを格納します。MathAbs関数は、入力の絶対値またはモジュラス値を返すことによって、どちらの価格が高いかにかかわらず、結果が正の数になることを保証するために使用されます。例えば、0.65432 - 0.05456 = -0.00024となります。答えは負を含みますが、関数は負符号を無視して0.00024を出力します。この場合も、その結果を、ある商品の可能な限り小さな価格変動であるポイントで割って、差のポイント形式を得ます。この例をもう一度使用すると、0.00024/0.00001=24.0となります。最後に、正確を期すために、NormalizeDouble関数を使用して浮動小数点数を指定された桁数に書式設定します。この場合ゼロなので、出力は整数になります。この例をもう一度使用すると、小数点を除けば24となります。

そして、その差が許容範囲である10以下かどうかを確認し、その結果をブール変数is_resistanceに格納します。同じロジックが支持レベルの確認にも適用されます。

レベルの条件が満たされれば、支持レベルと抵抗レベルはtrueとなり、そうでなければfalseとなります。レベルを確認するために、操作ログにプリントしてみましょう。すべてを確認するために、抵抗レベルの座標の価格を出力し、その価格差も一緒に出力して、条件を満たしていることを確認します。

         if (is_resistance){
            Print("RESISTANCE AT BAR ",i," (",high,") & ",j," (",high_j,"), Pts = ",high_diff);
          ...  
         }

以下がその結果です。

抵抗出力

レベルを特定することはできますが、それはどのレベルでも同じです。最高点または最低点にあるレベルが欲しいので、最も重要なレベルのみを考慮するようにするために、いくつかの特別な制御ロジックが必要になります。これを実現するには、対象となるバーの高値と安値をコピーし、それぞれ昇順と降順に並べ替え、必要なバーの最初と最後の金額をそれぞれ取得する必要があります。これはforループの前におこないます。

   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   int copiedBarsHighs = CopyHigh(_Symbol,_Period,1,visible_bars,pricesHighest);
   int copiedBarsLows = CopyLow(_Symbol,_Period,1,visible_bars,pricesLowest);

ストレージの前に、配列のデータをすべて解放します。次に、バーの高値をターゲット配列にコピーします。これは、CopyHigh整数データ型関数を使用して、銘柄、期間、コピーするバーの開始インデックス、バーの数、ターゲットストレージ配列を指定することで実現されます。コピーされたバーの量である結果は、整数変数copiedBarsHighsに代入されます。安値も同様です。データを確実に取得するために、ArrayPrint関数を使用して操作ログに配列を出力します。

   ArrayPrint(pricesHighest);
   ArrayPrint(pricesLowest);

これが私たちが得た結果です。

並び替えていないデータ

次に、ArraySort関数を使用してデータを昇順に並び替えて、結果を再度出力します。

         // sort the array in ascending order
   ArraySort(pricesHighest);
   ArraySort(pricesLowest);

   ArrayPrint(pricesHighest);
   ArrayPrint(pricesLowest);

これがその結果です。

昇順に並び替えられたデータ

最後に、高値の最初の10個の価格と安値の最後の10個の価格を取得する必要があります。

   ArrayRemove(pricesHighest,10,WHOLE_ARRAY);

最も高いバーの最初の10本の高値を取得するには、ArrayRemove関数を使用し、ターゲット配列、削除を開始するインデックス(この場合は10)、最後に削除する要素数(この場合は残りのデータ)を指定します。

最安値のバーの直近10本の安値を得るために、同様の操作をおこないますが、方法はより複雑です。

   ArrayRemove(pricesLowest,0,visible_bars-10);

同じ関数を使用しますが、最初の値には興味がないので、開始インデックスは0であり、カウントは検討中のバーの総数から10を引いたものです。データを出力すると、次のようになります。

最終固定必須データ

最も高いバーのデータが得られたので、引き続きレベルを確認し、有効な設定を決定することができます。別のforループを開始し、処理を実行します。

            for (int k=0; k<ArraySize(pricesHighest); k++){
            ...
            }

今回は、配列内のすべての価格を考慮したいので、カウンタ変数kはゼロから始まります。 

価格の一致を見つけたいので、マッチ結果のフラグを格納するブールストレージ変数をforループの外で宣言し、falseに初期化します。

   bool matchFound_high1 = false, matchFound_low1 = false;
   bool matchFound_high2 = false, matchFound_low2 = false;

選択された保存価格が最初のループのバーの高値と等しい場合、最初に見つかった高値のフラグをtrueに設定し、インスタンスを通知します。同様に、選択された保存価格が2番目のループのバーの高値と等しい場合、2番目に見つかった高値のフラグをtrueに設定し、インスタンスを通知します。

               if (pricesHighest[k]==high){
                  matchFound_high1 = true;
                  Print("> RES H1(",high,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (pricesHighest[k]==high_j){
                  matchFound_high2 = true;
                  Print("> RES H2(",high_j,") FOUND @ ",k," (",pricesHighest[k],")");
               }

2つの座標の一致が見つかっても、現在の抵抗レベルが価格と等しい場合は、すでにレベルがあることを意味します。これ以上抵抗レベルを作る必要はありません。インスタンスを通知し、stop_processingフラグをtrueに設定し、ループから早期に抜け出します。

               if (matchFound_high1 && matchFound_high2){
                  if (resistanceLevels[0]==high || resistanceLevels[1]==high_j){
                     Print("CONFIRMED BUT This is the same resistance level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  ...
                  
               }

stop_processingフラグは、上部のforループの外側で定義され、リソースを確実に節約するために、最初のforループの実行ロジックに組み込まれています。

   bool stop_processing = false; // Flag to control outer loop

//...
   for (int i=1; i<=visible_bars-1 && !stop_processing; i++){
      ...
   
   }

そうでなければ、2つの座標の一致が見つかりましたが、それらが現在の価格と等しくない場合は、別の新しい抵抗レベルがあることを意味するので、最新のデータに更新することができます。 

                  else {
                     Print(" ++++++++++ RESISTANCE LEVELS CONFIRMED @ BARS ",i,
                     "(",high,") & ",j,"(",high_j,")");
                     resistanceLevels[0] = high;
                     resistanceLevels[1] = high_j;
                     ArrayPrint(resistanceLevels);
                     
                     ...
                  }

以下は、得られた結果を可視化したものです。

確認された抵抗レベル

レベルを可視化するために、チャートにマッピングしてみましょう。

                     draw_S_R_Level(resLine,high,colorRes,5);
                     draw_S_R_Level_Point(resline_prefix,high,time,218,-1,colorRes,90);
                     draw_S_R_Level_Point(resline_prefix,high,time_j,218,-1,colorRes,90);

                     stop_processing = true; // Set the flag to stop processing
                     break;

この仕事には2つの関数を使用します。最初の関数draw_S_R_Levelは、描画するレベルの名前、価格、色、線の幅を受け取ります。

void draw_S_R_Level(string levelName,double price,color clr,int width){
   if (ObjectFind(0,levelName) < 0){
      ObjectCreate(0,levelName,OBJ_HLINE,0,TimeCurrent(),price);
      ObjectSetInteger(0,levelName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,levelName,OBJPROP_WIDTH,width);
   }
   else {
      ObjectSetDouble(0,levelName,OBJPROP_PRICE,price);
   }
   ChartRedraw(0);
}

この関数はvoidデータ型で、何も返す必要がありません。オブジェクトが見つからない場合は負の整数を返すObjectFind関数を使用して、条件文を使用してオブジェクトが存在するかどうかを確認します。存在する場合、必要なのは単一の座標だけなので、現在の時刻と指定された価格に対してOBJ_HLINEとして識別されるオブジェクトの作成に進みます。次に色と幅を設定します。オブジェクトが見つかれば、その価格を指定された価格に更新し、現在の変更を適用するためにチャートを再描画するだけです。この関数はチャートに水平線を引くだけです。これがその結果です。

平抵抗線

2つ目の関数draw_S_R_Level_Pointは、描画する線の名前、価格、時間、矢印コード、方向、色、説明ラベルの角度を受け取ります。この関数は、描画された抵抗線上でより明確になるようにレベルポイントを描画します。
void draw_S_R_Level_Point(string objName,double price,datetime time,
      int arrowcode,int direction,color clr,double angle){
   //objName = " ";
   StringConcatenate(objName,objName," @ \nTime: ",time,"\nPrice: ",DoubleToString(price,_Digits));
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)) {
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      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 prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }
   ChartRedraw(0);
}

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

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

この関数はまず、レベルポイントを区別するために、オブジェクトの名前と時間、価格を連結します。これにより、ラベルにカーソルを合わせると、座標の固有の時間と価格を含む説明がポップアップ表示されます。

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

WINGDINGS

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

抵抗+矢印

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

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

   string prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }

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

抵抗+矢印+説明

同時に、支持レベルをマッピングするために、同じロジックが適用されるが、逆条件が適用されます。

         else if (is_support){
            //Print("SUPPORT AT BAR ",i," (",low,") & ",j," (",low_j,"), Pts = ",low_diff);
            
            for (int k=0; k<ArraySize(pricesLowest); k++){
               if (pricesLowest[k]==low){
                  matchFound_low1 = true;
                  //Print("> SUP L1(",low,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (pricesLowest[k]==low_j){
                  matchFound_low2 = true;
                  //Print("> SUP L2(",low_j,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (matchFound_low1 && matchFound_low2){
                  if (supportLevels[0]==low || supportLevels[1]==low_j){
                     Print("CONFIRMED BUT This is the same support level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  else {
                     Print(" ++++++++++ SUPPORT LEVELS CONFIRMED @ BARS ",i,
                     "(",low,") & ",j,"(",low_j,")");
                     supportLevels[0] = low;
                     supportLevels[1] = low_j;
                     ArrayPrint(supportLevels);
                     
                     draw_S_R_Level(supLine,low,colorSup,5);
                     draw_S_R_Level_Point(supline_prefix,low,time,217,1,colorSup,-90);
                     draw_S_R_Level_Point(supline_prefix,low,time_j,217,1,colorSup,-90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }

レベルの識別とそれぞれのチャートマイルストーンへのマッピングによって得られる最終結果は以下の通りです。

支持レベルと抵抗レベル

その後、レベルを監視し、レベルが可視バーから近接しなくなった場合、抵抗レベルは無効とみなし、削除します。そのためには、オブジェクトレベルのラインを見つける必要があり、見つかったら、その条件が妥当かどうかを確認します。

   if (ObjectFind(0,resLine) >= 0){
      double objPrice = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      double visibleHighs[];
      ArraySetAsSeries(visibleHighs,true);
      CopyHigh(_Symbol,_Period,1,visible_bars,visibleHighs);
      //Print("Object Found & visible bars is: ",ArraySize(visibleHighs));
      //ArrayPrint(visibleHighs);
      bool matchHighFound = false;

      ...
   }

ここで、抵抗線オブジェクトが見つかったかどうかを確認し、見つかった場合はその価格を取得します。再度、チャート上の可視バーの高値をコピーし、visibleHighs double配列変数に格納します。

その後、高値を経由してループし、現在選択されているバーの価格と抵抗ラインの価格が一致するかどうかを調べます。一致する場合は、matchHighFoundフラグをtrueにセットし、ループを終了します。

      for (int i=0; i<ArraySize(visibleHighs); i++){
         if (visibleHighs[i] == objPrice){
            Print("> Match price for resistance found at bar # ",i+1," (",objPrice,")");
            matchHighFound = true;
            break;
         }
      }

一致しない場合は、抵抗レベルが近接していないことを意味します。インスタンスを通知し、カスタム関数を使用してオブジェクトを削除します。

      if (!matchHighFound){
         Print("(",objPrice,") > Match price for the resistance line not found. Delete!");
         deleteLevel(resLine);
      }

カスタム関数deleteLevelは、1つの引数(削除するレベル名)を取り、定義されたオブジェクトを削除するためにObjectDelete関数を使用します。

void deleteLevel(string levelName){
   ObjectDelete(0,levelName);
   ChartRedraw(0);
}

同じロジックが支持レベルにも当てはまりますが、逆の条件が優勢です。

   if (ObjectFind(0,supLine) >= 0){
      double objPrice = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      double visibleLows[];
      ArraySetAsSeries(visibleLows,true);
      CopyLow(_Symbol,_Period,1,visible_bars,visibleLows);
      //Print("Object Found & visible bars is: ",ArraySize(visibleLows));
      //ArrayPrint(visibleLows);
      bool matchLowFound = false;
      
      for (int i=0; i<ArraySize(visibleLows); i++){
         if (visibleLows[i] == objPrice){
            Print("> Match price for support found at bar # ",i+1," (",objPrice,")");
            matchLowFound = true;
            break;
         }
      }
      if (!matchLowFound){
         Print("(",objPrice,") > Match price for the support line not found. Delete!");
         deleteLevel(supLine);
      }
   }

最後に、ここまでで抵抗レベルと支持レベルがチャート内に残っていれば、それは有効であることを意味するので、それをブレイクしたかどうかを判断するロジックを作り続け、市場ポジションを建てることができます。まず、抵抗レベルブレイクについて考えてみましょう。

抵抗レベルを何度もブレイクしてシグナルが何度も発生する可能性があるため、一度抵抗レベルをブレイクしてシグナルを発生させたら、その後にブレイクした抵抗レベルが全く同じレベルであれば、再びシグナルが発生しないようにするロジックが必要です。これを実現するために、シグナルの価格を保持する静的なdouble変数を宣言します。

   static double ResistancePriceTrade = 0;
   if (ObjectFind(0,resLine) >= 0){
      double ResistancePriceLevel = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      if (ResistancePriceTrade != ResistancePriceLevel){
      ...

   }

そして、抵抗線の存在を確認し、抵抗線が存在すれば、その価格を求めます。条件文を使用して、シグナルが抵抗線の価格と等しくないかどうかを確認します。つまり、その特定のレベルのシグナルはまだ生成されておらず、ブレイクアウトシグナルの確認に進むことができます。確認をおこなうには、前のバーのデータと更新された価格相場が必要です。

         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

次に、条件文を使用して、価格が抵抗レベルを上抜けるかどうかを確認します。もしそうなら、売りシグナルを操作ログに出力することで知らせます。次に、取引オブジェクトとドット演算子を使用して売りエントリメソッドにアクセスし、必要なパラメータを指定します。最後に、シグナル変数の値を現在の抵抗レベルに更新し、同じ抵抗レベルに基づいて別のシグナルをわざわざ生成しないようにします。

         if (open1 > close1 && open1 < ResistancePriceLevel
            && high1 > ResistancePriceLevel && Bid < ResistancePriceLevel){
            Print("$$$$$$$$$$$$ SELL NOW SIGNAL!");
            obj_Trade.Sell(0.01,_Symbol,Bid,Bid+350*5*_Point,Bid-350*_Point);
            ResistancePriceTrade = ResistancePriceLevel;
         }

同じロジックが支持ブレイクアウトのロジックにも当てはまりますが、逆の条件が優勢です。

   static double SupportPriceTrade = 0;
   if (ObjectFind(0,supLine) >= 0){
      double SupportPriceLevel = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      if (SupportPriceTrade != SupportPriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 < close1 && open1 > SupportPriceLevel
            && low1 < SupportPriceLevel && Ask > SupportPriceLevel){
            Print("$$$$$$$$$$$$ BUY NOW SIGNAL!");
            obj_Trade.Buy(0.01,_Symbol,Ask,Ask-350*5*_Point,Ask+350*_Point);
            SupportPriceTrade = SupportPriceLevel;
         }
         
      }
   }

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

完全な支持と抵抗取引

以下は、MQL5で支持と抵抗のFX取引戦略を作成するために必要な完全なコードで、それぞれレベルを特定し、チャートにマッピングし、市場ポジションを建てます。

//+------------------------------------------------------------------+
//|                                       RESISTANCE AND SUPPORT.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;

//bool stop_processing = false;

double pricesHighest[], pricesLowest[];

double resistanceLevels[2], supportLevels[2];


#define resLine "RESISTANCE LEVEL"
#define colorRes clrRed
#define resline_prefix "R"

#define supLine "SUPPORT LEVEL"
#define colorSup clrBlue
#define supline_prefix "S"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   ArraySetAsSeries(pricesHighest,true);
   ArraySetAsSeries(pricesLowest,true);
   // define the size of the arrays
   ArrayResize(pricesHighest,50);
   ArrayResize(pricesLowest,50);
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   //ArrayFree(resistanceLevels); // cannot be used for static allocated array
   //ArrayFree(supportLevels); // cannot be used for static allocated array
   
   ArrayRemove(resistanceLevels,0,WHOLE_ARRAY);
   ArrayRemove(supportLevels,0,WHOLE_ARRAY);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   
   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;
   
   int visible_bars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   bool stop_processing = false; // Flag to control outer loop
   bool matchFound_high1 = false, matchFound_low1 = false;
   bool matchFound_high2 = false, matchFound_low2 = false;
   
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   int copiedBarsHighs = CopyHigh(_Symbol,_Period,1,visible_bars,pricesHighest);
   int copiedBarsLows = CopyLow(_Symbol,_Period,1,visible_bars,pricesLowest);
   
   //ArrayPrint(pricesHighest);
   //ArrayPrint(pricesLowest);
         // sort the array in ascending order
   ArraySort(pricesHighest);
   ArraySort(pricesLowest);
   //ArrayPrint(pricesHighest);
   //ArrayPrint(pricesLowest);
   ArrayRemove(pricesHighest,10,WHOLE_ARRAY);
   ArrayRemove(pricesLowest,0,visible_bars-10);
   //Print("FIRST 10 HIGHEST PRICES:");
   //ArrayPrint(pricesHighest);
   //Print("LAST 10 LOWEST PRICES:");
   //ArrayPrint(pricesLowest);
   
   for (int i=1; i<=visible_bars-1 && !stop_processing; i++){
      //Print(":: BAR NO: ",i);
      double open = iOpen(_Symbol,_Period,i);
      double high = iHigh(_Symbol,_Period,i);
      double low = iLow(_Symbol,_Period,i);
      double close = iClose(_Symbol,_Period,i);
      datetime time = iTime(_Symbol,_Period,i);
      
      int diff_i_j = 10;
      
      for (int j=i+diff_i_j; j<=visible_bars-1; j++){
         //Print("BAR CHECK NO: ",j);
         double open_j = iOpen(_Symbol,_Period,j);
         double high_j = iHigh(_Symbol,_Period,j);
         double low_j = iLow(_Symbol,_Period,j);
         double close_j = iClose(_Symbol,_Period,j);
         datetime time_j = iTime(_Symbol,_Period,j);
         
         // CHECK FOR RESISTANCE
         double high_diff = NormalizeDouble((MathAbs(high-high_j)/_Point),0);
         bool is_resistance = high_diff <= 10;
         
         // CHECK FOR SUPPORT
         double low_diff = NormalizeDouble((MathAbs(low-low_j)/_Point),0);
         bool is_support = low_diff <= 10;
         
         if (is_resistance){
            //Print("RESISTANCE AT BAR ",i," (",high,") & ",j," (",high_j,"), Pts = ",high_diff);
            
            for (int k=0; k<ArraySize(pricesHighest); k++){
               if (pricesHighest[k]==high){
                  matchFound_high1 = true;
                  //Print("> RES H1(",high,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (pricesHighest[k]==high_j){
                  matchFound_high2 = true;
                  //Print("> RES H2(",high_j,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (matchFound_high1 && matchFound_high2){
                  if (resistanceLevels[0]==high || resistanceLevels[1]==high_j){
                     Print("CONFIRMED BUT This is the same resistance level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurily
                  }
                  else {
                     Print(" ++++++++++ RESISTANCE LEVELS CONFIRMED @ BARS ",i,
                     "(",high,") & ",j,"(",high_j,")");
                     resistanceLevels[0] = high;
                     resistanceLevels[1] = high_j;
                     ArrayPrint(resistanceLevels);
                     
                     draw_S_R_Level(resLine,high,colorRes,5);
                     draw_S_R_Level_Point(resline_prefix,high,time,218,-1,colorRes,90);
                     draw_S_R_Level_Point(resline_prefix,high,time_j,218,-1,colorRes,90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }
         
         else if (is_support){
            //Print("SUPPORT AT BAR ",i," (",low,") & ",j," (",low_j,"), Pts = ",low_diff);
            
            for (int k=0; k<ArraySize(pricesLowest); k++){
               if (pricesLowest[k]==low){
                  matchFound_low1 = true;
                  //Print("> SUP L1(",low,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (pricesLowest[k]==low_j){
                  matchFound_low2 = true;
                  //Print("> SUP L2(",low_j,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (matchFound_low1 && matchFound_low2){
                  if (supportLevels[0]==low || supportLevels[1]==low_j){
                     Print("CONFIRMED BUT This is the same support level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  else {
                     Print(" ++++++++++ SUPPORT LEVELS CONFIRMED @ BARS ",i,
                     "(",low,") & ",j,"(",low_j,")");
                     supportLevels[0] = low;
                     supportLevels[1] = low_j;
                     ArrayPrint(supportLevels);
                     
                     draw_S_R_Level(supLine,low,colorSup,5);
                     draw_S_R_Level_Point(supline_prefix,low,time,217,1,colorSup,-90);
                     draw_S_R_Level_Point(supline_prefix,low,time_j,217,1,colorSup,-90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }
         
         
         
         if (stop_processing){break;}
      }
      if (stop_processing){break;}
   }
   
   if (ObjectFind(0,resLine) >= 0){
      double objPrice = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      double visibleHighs[];
      ArraySetAsSeries(visibleHighs,true);
      CopyHigh(_Symbol,_Period,1,visible_bars,visibleHighs);
      //Print("Object Found & visible bars is: ",ArraySize(visibleHighs));
      //ArrayPrint(visibleHighs);
      bool matchHighFound = false;
      
      for (int i=0; i<ArraySize(visibleHighs); i++){
         if (visibleHighs[i] == objPrice){
            Print("> Match price for resistance found at bar # ",i+1," (",objPrice,")");
            matchHighFound = true;
            break;
         }
      }
      if (!matchHighFound){
         Print("(",objPrice,") > Match price for the resistance line not found. Delete!");
         deleteLevel(resLine);
      }
   }
   
   if (ObjectFind(0,supLine) >= 0){
      double objPrice = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      double visibleLows[];
      ArraySetAsSeries(visibleLows,true);
      CopyLow(_Symbol,_Period,1,visible_bars,visibleLows);
      //Print("Object Found & visible bars is: ",ArraySize(visibleLows));
      //ArrayPrint(visibleLows);
      bool matchLowFound = false;
      
      for (int i=0; i<ArraySize(visibleLows); i++){
         if (visibleLows[i] == objPrice){
            Print("> Match price for support found at bar # ",i+1," (",objPrice,")");
            matchLowFound = true;
            break;
         }
      }
      if (!matchLowFound){
         Print("(",objPrice,") > Match price for the support line not found. Delete!");
         deleteLevel(supLine);
      }
   }
   
   static double ResistancePriceTrade = 0;
   if (ObjectFind(0,resLine) >= 0){
      double ResistancePriceLevel = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      if (ResistancePriceTrade != ResistancePriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 > close1 && open1 < ResistancePriceLevel
            && high1 > ResistancePriceLevel && Bid < ResistancePriceLevel){
            Print("$$$$$$$$$$$$ SELL NOW SIGNAL!");
            obj_Trade.Sell(0.01,_Symbol,Bid,Bid+350*5*_Point,Bid-350*_Point);
            ResistancePriceTrade = ResistancePriceLevel;
         }
         
      }
   }
   
   static double SupportPriceTrade = 0;
   if (ObjectFind(0,supLine) >= 0){
      double SupportPriceLevel = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      if (SupportPriceTrade != SupportPriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 < close1 && open1 > SupportPriceLevel
            && low1 < SupportPriceLevel && Ask > SupportPriceLevel){
            Print("$$$$$$$$$$$$ BUY NOW SIGNAL!");
            obj_Trade.Buy(0.01,_Symbol,Ask,Ask-350*5*_Point,Ask+350*_Point);
            SupportPriceTrade = SupportPriceLevel;
         }
         
      }
   }
   
}
//+------------------------------------------------------------------+

void draw_S_R_Level(string levelName,double price,color clr,int width){
   if (ObjectFind(0,levelName) < 0){
      ObjectCreate(0,levelName,OBJ_HLINE,0,TimeCurrent(),price);
      ObjectSetInteger(0,levelName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,levelName,OBJPROP_WIDTH,width);
   }
   else {
      ObjectSetDouble(0,levelName,OBJPROP_PRICE,price);
   }
   ChartRedraw(0);
}

void deleteLevel(string levelName){
   ObjectDelete(0,levelName);
   ChartRedraw(0);
}

void draw_S_R_Level_Point(string objName,double price,datetime time,
      int arrowcode,int direction,color clr,double angle){
   //objName = " ";
   StringConcatenate(objName,objName," @ \nTime: ",time,"\nPrice: ",DoubleToString(price,_Digits));
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)) {
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      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 prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }
   ChartRedraw(0);
}

お疲れ様です。支持&抵抗の外国為替取引戦略に基づいた純粋なプライスアクション取引システムを作成し、取引シグナルを生成するだけでなく、生成されたシグナルに基づいて市場ポジションを開くこともできます。


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

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

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

グラフ

  • バックテスト結果

結果


結論

結論として、これまで見てきたように、支持&抵抗のFX取引戦略の自動化は可能であり、簡単です。必要なのは、戦略とその設計図を明確に理解し、その知識を突破口にすることです。MQL5 言語の強力な機能を自信を持って活用し、正確で効率的な取引戦略を立てました。分析とEAの作成は、自動化が貴重な時間を節約するだけでなく、ヒューマンエラーや感情的な干渉を減らすことによって取引の有効性を高めることを示しています。

免責条項: この記事で説明されている情報は、あくまでも教育目的です。これは、純粋な価格アプローチに基づいて支持&抵抗のEAを作成する方法についての洞察を示すことを意図しているだけです。より多くの最適化とデータ抽出を考慮に入れたより良いEAを作成するためのベースとして使用してください。提示された情報は、いかなる取引結果も保証するものではありません。

この記事が、支持&抵抗EAの自動化に有益なものであったことを心から願っています。このような自動システム統合は、金融市場がさらに発展するにつれて確実に頻度を増し、市場力学のあらゆる側面を扱う最先端の手段をトレーダーに提供します。MQL5のようなテクノロジーが進化を続け、より複雑でインテリジェントな取引ソリューションへの扉を開くことで、取引の未来は明るいと思われる。


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

添付されたファイル |
PythonとMQL5を使用した取引戦略の自動パラメータ最適化 PythonとMQL5を使用した取引戦略の自動パラメータ最適化
取引戦略とパラメータを自己最適化するアルゴリズムには、いくつかの種類があります。これらのアルゴリズムは、過去と現在の市場データに基づいて取引戦略を自動的に改善するために使用されます。この記事では、そのうちの1つをpythonとMQL5の例で見ていきます。
知っておくべきMQL5ウィザードのテクニック(第23回):CNN 知っておくべきMQL5ウィザードのテクニック(第23回):CNN
畳み込みニューラルネットワーク(Convolutional Neural Network: CNN)もまた、多次元のデータセットを主要な構成要素に分解することに特化した機械学習アルゴリズムです。一般的にどのように達成されるかを見て、別のMQL5ウィザードシグナルクラスのトレーダーへの応用の可能性を探ります。
リプレイシステムの開発(第42回):Chart Traderプロジェクト(I) リプレイシステムの開発(第42回):Chart Traderプロジェクト(I)
もっと面白いものを作りましょう。ネタバレはしたくないので、理解を深めるために記事を読んでください。リプレイ/シミュレーターシステムの開発に関する本連載の最初の段階から、私は、開発中のシステムと実際の市場の両方で同じようにMetaTrader 5プラットフォームを使用することがアイディアであると述べてきました。これが適切におこなわれることが重要です。ある道具を使用して訓練して戦い方を学んだ末、戦いの最中に別の道具を使用しなければならないというようなことは誰もしたくありません。
MQL5でゾーン回復マーチンゲール戦略を開発する MQL5でゾーン回復マーチンゲール戦略を開発する
この記事では、ゾーン回復取引アルゴリズムに基づくエキスパートアドバイザー(EA)の作成に向けて実施すべきステップについて、詳細な観点から論じています。これは、アルゴリズムトレーダーの時間を節約するシステムの自動化に役立ちます。