MQL5入門(第26回):サポートおよびレジスタンスゾーンを使ったEAの構築
はじめに
連載「MQL5入門」の第26回へようこそ。この記事では、テクニカル分析における最も基本的な概念のひとつであるサポートおよびレジスタンスゾーンに焦点を当て、これらの重要な価格レベルに基づいて取引をおこなうEAの作り方を解説します。重要な取引判断は、多くの場合、サポートやレジスタンスゾーンと呼ばれる心理的障壁で市場参加者によっておこなわれます。レジスタンスは、上昇圧力を抑え込む売り圧力が強いレベルを示します。一方、サポートは、売り圧力を上回る買い圧力が働き、価格が反発する傾向にあるレベルを示します。
本連載第24回では、チャート上の矩形オブジェクトを用いて、サポートおよびレジスタンスゾーンを手動で設定する方法を学びました。これにより、描画したゾーンに反応する半自動取引システムを作ることができました。しかし、人間の介入なしでチャート上のすべてのサポートおよびレジスタンスゾーンをEAに自動的に認識させたい場合はどうすればよいでしょうか。この記事では、その方法について詳しく解説します。いつものように、MQL5の原則をプロジェクトベースで、初心者にもわかりやすく実践的に説明します。プログラムでゾーンを特定し、その周辺の価格変動を追跡し、可能な反転に適切に反応するEAを作成する方法を学びます。
EAの動作概要
本プロジェクトでは、指定したバー数の範囲内で自動的にサポートおよびレジスタンスゾーンを検索するEAを作成しています。手動で描画されたゾーンに依存するのではなく、EAは直近の価格変動を分析し、市場が繰り返し反応した箇所、つまり価格が上方向に跳ね返った(サポート)あるいは下方向に反転した(レジスタンス)領域を検出します。
サポート
すべてのスイングローを見つけるために、EAはまず指定されたバー数をループします。スイングローとは、直前および直後のローソク足の安値よりも低い安値を持つローソク足で、価格の一時的な底を示します。
スイングローを特定した後、EAはそのローソク足の始値と終値のうちの最小値およびローソク足自体の正確な安値を確認します。この2つの値が潜在的なサポートゾーンを定義しますが、まだ検証はされていません。その後、このスイングロー以降の時間を確認し、同じゾーン内に別のスイングローが形成されているかどうかを調べます。最初のスイングローを形成したローソク足の始値と終値のうち低い方の価格が、2番目のスイングローの安値よりも高い必要があります。この繰り返しにより、市場が再びその領域を確認したことが示され、ゾーンの重要性が高まります。
サポートゾーンを特定した後、EAはそのゾーンを下抜けたローソク足がないかを確認します。もし閉値がゾーンを下回ったローソク足があれば、そのゾーンは無効とされ、考慮されません。 ゾーンが維持されている場合、EAは下位時間足に移動し、強気のCHoCH (Change of Character)を監視します。この構造変化が確認された場合、買いトレードが開始されます。これは市場が買い手によって支配され、サポートゾーンが堅固であることを示しています。

レジスタンス
すべてのスイングハイを見つけるために、EAはまず指定されたバー数をループします。スイングハイとは、直前および直後のローソク足の高値よりも高い高値を持つローソク足で、価格の一時的な天井を示します。
スイングハイを特定した後、EAはそのローソク足の高値および始値と終値のうちの高い方の値を記録します。これらの値は潜在的なレジスタンスゾーンを示すものですが、まだ検証はされていません。その後、EAはスイングハイ発生時点以降の価格を確認し、同じゾーン内に別のスイングハイが形成されているかどうかを調べます。最初のスイングハイを形成したローソク足の始値と終値のうち高い方の価格が、2番目のスイングハイの高値よりも低い(または同一ゾーン内にある)必要があります。この繰り返しによる価格の反発は強い売り圧力を示し、その領域が障壁として機能する可能性を確認します。
EAは次に、記録されたレジスタンスゾーンを上抜けたローソク足がないかを確認します。閉値がゾーンを上回ったローソク足があれば、そのゾーンは無効となり無視されます。ゾーンが有効である場合、EAは下位時間足に移動し、弱気のCHoCHを監視します。この構造変化が確認された場合、売りエントリーをおこないます。これは、レジスタンスレベルから売り手が再び支配を取り戻したことを示しています。

最初のスイングローの特定
プロジェクトについての理解が深まったので、次のステップとしてサポートゾーンを特定します。このプロセスの第一段階は、チャート上でいくつかの重要なスイングローを見つけることです。これらのスイングローは、価格の下落が一時的に止まり、その後上昇へと転じたポイントを示しており、買い圧力が存在する可能性のある領域を示唆します。そのため、強気のセットアップにおけるエントリーポイントとして機能する可能性のあるサポートゾーンを特定する手がかりとなります。
まず、関連するローソク足データを複製します。次に、サポートおよびレジスタンスゾーンを特定するために、EAが検索するバーの本数を指定します。その後、指定された範囲内でスイングハイおよびスイングローを判定する関数を開発します。この機能は、信頼性の高いサポートおよびレジスタンスレベルを決定するために不可欠な、重要な価格の転換点を検出する基盤となります。
例input ENUM_TIMEFRAMES timeframe = PERIOD_W1; //SUPPORT AND RESISTANCE TIMEFRAME double open[]; double close[]; double low[]; double high[]; datetime time[]; int bars_check = 200; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- CopyOpen(_Symbol, timeframe, TimeCurrent(), bars_check, open); CopyClose(_Symbol, timeframe, TimeCurrent(), bars_check, close); CopyLow(_Symbol, timeframe, TimeCurrent(), bars_check, low); CopyHigh(_Symbol, timeframe, TimeCurrent(), bars_check, high); CopyTime(_Symbol, timeframe, TimeCurrent(), bars_check, time); } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING LOW | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i]) return false; } return true; } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING HIGH | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i]) return false; // If the current high is not the highest, return false. } return true; }
説明
ユーザーは、レジスタンスゾーンおよびサポートゾーンを検索するための時間足を選択できます。 配列にはローソク足データが格納されます。bars_checkオプションに基づき、EAはスイングポイントを検出するために200本のローソク足を対象として分析をおこないます。 チャート上の各ローソク足について、価格情報および時間情報がプログラムによってコピーされます。最初の引数である_Symbolは、現在チャートに適用されている通貨ペア(または銘柄)のデータを取得するようEAに指示します。2番目の引数は時間足(timeframe)で、ユーザーが選択した分析用の時間足を指定します。このパラメータはinputとして定義されているため、ユーザーは任意の時間足を分析対象として指定できます。次の引数は、データのコピーを開始する位置を指定するもので、ここではTimeCurrent()関数を使用しています。
この関数は現在のサーバー時刻を返すため、EAは最新のローソク足からデータのコピーを開始します。EAがコピーするローソク足の本数はbars_check引数によって決定され、この例では現在時点から過去200本分のバーのデータが取得されます。最後の引数は、コピーされたデータを格納する配列(open、close、low、high、timeのいずれか)を指定するもので、EAはこれを用いて後続の分析が可能な形でデータを整理します。
IsSwingLow()関数には3つの引数が必要です。最初の引数は、各ローソク足の安値を格納したlow_price[]配列です。2番目の引数indexは、現在分析対象となっているローソク足を示します。3番目の引数lookbackは、現在のローソク足の前後で比較するローソク足の本数を決定します。関数内では、1からlookbackまでの範囲でループを実行し、現在のローソク足の安値と周辺のローソク足の安値を比較します。現在の安値が隣接するいずれかの安値よりも高い場合、そのローソク足はスイングローではないと判断され、関数はfalseを返します。すべての隣接ローソク足と比較して最も低い値を維持している場合、そのローソク足は正当なスイングローと判断され、trueが返されます。
IsSwingHigh()関数は、この処理の逆のロジックで動作します。この関数ではhigh_price[]配列を使用し、現在のローソク足の高値が、指定されたlookback範囲内の隣接ローソク足の高値よりも高いかどうかを判定します。現在の高値が隣接するいずれかの高値よりも低い場合はfalseを返し、そうでなければスイングハイとして認識されtrueを返します。
ローソク足データのコピーが完了した後、次のステップとしてチャート上のすべてのスイングローを検出します。これらのスイングローは、価格が一時的に反転上昇したポイントであり、潜在的なサポートゾーンを形成する位置となります。検出された各スイングローは、サポートゾーンを構築するための最初の基準点として使用されます。その後EAは、同じ価格帯内での追加のタッチや反応を確認することで、これらのゾーンの有効性を検証します。
例input ENUM_TIMEFRAMES timeframe = PERIOD_W1; //SUPPORT AND RESISTANCE TIMEFRAME double open[]; double close[]; double low[]; double high[]; datetime time[]; int bars_check = 200; double first_sup_price; double first_sup_min_body_price; datetime first_sup_time; string support_object; ulong chart_id = ChartID(); int total_symbol_bars; int z = 7; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- ObjectsDeleteAll(chart_id); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- CopyOpen(_Symbol, timeframe, TimeCurrent(), bars_check, open); CopyClose(_Symbol, timeframe, TimeCurrent(), bars_check, close); CopyLow(_Symbol, timeframe, TimeCurrent(), bars_check, low); CopyHigh(_Symbol, timeframe, TimeCurrent(), bars_check, high); CopyTime(_Symbol, timeframe, TimeCurrent(), bars_check, time); total_symbol_bars = Bars(_Symbol, timeframe); if(total_symbol_bars >= bars_check) { for(int i = z ; i < bars_check - z; i++) { if(IsSwingLow(low, i, z)) { first_sup_price = low[i]; first_sup_min_body_price = MathMin(close[i], open[i]); first_sup_time = time[i]; support_object = StringFormat("SUPPORT %f",first_sup_price); ObjectCreate(chart_id,support_object,OBJ_RECTANGLE,0,first_sup_time,first_sup_price,TimeCurrent(),first_sup_min_body_price); ObjectSetInteger(chart_id,support_object,OBJPROP_COLOR,clrBlue); ObjectSetInteger(chart_id,support_object,OBJPROP_BACK,true); ObjectSetInteger(chart_id,support_object,OBJPROP_FILL,true); } } } } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING LOW | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i]) return false; } return true; } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING HIGH | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i]) return false; // If the current high is not the highest, return false. } return true; }
出力

説明
検出された各スイングローには、重要な特徴を保持するための変数を定義し、基本的な情報を保存します。具体的には、スイングローとなったローソク足の実際の最安値、スイングローが発生した時刻、そして始値と終値のうち小さい方の値として定義されるローソク足実体の下限値が記録されます。
各スイングローについて、発生時刻、ローソク足実体の下端、実際の最安値といった重要な情報を管理します。さらに、チャート上のグラフィカルオブジェクトを制御するためにチャートIDを取得し、サポートゾーンを表す矩形オブジェクトの名前を保存するための変数も用意します。7本のローソク足を参照するルックバック期間を用いて、正当なスイングローを判定します。チャート上に少なくとも200本のローソク足が存在することを確認した後、プログラムは7本目のローソク足からデータの反復処理を開始します。
ループ内では、スイングロー判定関数を使用して、各ローソク足がスイングローのパターンを形成しているかどうかを確認します。スイングローが検出された場合、プログラムはそのローソク足のタイムスタンプ、最安値、そして始値と終値のうち小さい方の値を記録します。その後、「SUPPORT」という文字列と実際のサポート価格を組み合わせることで、サポートゾーンに一意の名称を付与します。
コンピュータは、各ローソク足の安値が、その前後7本のローソク足の安値よりも低いかどうかを調べることで、自動的にスイングローを検出します。スイングローが特定されると、サポートゾーンを設定するために、そのローソク足の正確な発生時刻、最安値、そして始値と終値のうち小さい方の値が記録されます。これらの情報をもとに、スイングローの時点から現在のローソク足まで延びる矩形がチャート上に描画され、色や塗りつぶしを用いて視覚的に表現されます。さらに、各サポートゾーンには、価格と「SUPPORT」を組み合わせた一意の名前が付けられ、チャート上で容易に識別できるようになります。
プログラムは、スイングローの価格とローソク足実体の下限価格の間の範囲を、スイングロー発生時刻から現在時刻まで横に延ばした矩形として描画し、チャート上にサポートゾーンを表示します。
2番目のスイングローの特定
次の段階では、最初のスイングローによって定義されたサポートゾーン内に、別のスイングローが形成されているかどうかを判定します。これは、サポートゾーンの真正性および強度を裏付ける重要な確認プロセスです。最初のスイングローの時点から、EAは、前もって決定されたサポートゾーンの範囲内に収まる2番目のスイングローの出現を監視します。つまり、最初のスイングローのローソク足の安値と、そのローソク足の実体の安値の間に位置する場合です。この条件が満たされると、市場が同一の価格帯を繰り返し尊重していることを意味し、サポートゾーンの信頼性が高まります。
例double second_sup_price; datetime second_sup_time; string first_low_txt; string second_low_txt;
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- CopyOpen(_Symbol, timeframe, TimeCurrent(), bars_check, open); CopyClose(_Symbol, timeframe, TimeCurrent(), bars_check, close); CopyLow(_Symbol, timeframe, TimeCurrent(), bars_check, low); CopyHigh(_Symbol, timeframe, TimeCurrent(), bars_check, high); CopyTime(_Symbol, timeframe, TimeCurrent(), bars_check, time); total_symbol_bars = Bars(_Symbol, timeframe); if(total_symbol_bars >= bars_check) { for(int i = z ; i < bars_check - z; i++) { if(IsSwingLow(low, i, z)) { first_sup_price = low[i]; first_sup_min_body_price = MathMin(close[i], open[i]); first_sup_time = time[i]; for(int j = i+1; j < bars_check - z; j++) { if(IsSwingLow(low, j, z) && low[j] <= first_sup_min_body_price && low[j] >= first_sup_price) { second_sup_price = low[j]; second_sup_time = time[j]; support_object = StringFormat("SUPPORT %f",first_sup_price); ObjectCreate(chart_id,support_object,OBJ_RECTANGLE,0,first_sup_time,first_sup_price,TimeCurrent(),first_sup_min_body_price); ObjectSetInteger(chart_id,support_object,OBJPROP_COLOR,clrBlue); ObjectSetInteger(chart_id,support_object,OBJPROP_BACK,true); ObjectSetInteger(chart_id,support_object,OBJPROP_FILL,true); first_low_txt = StringFormat("FIRST LOW%d",i); ObjectCreate(chart_id,first_low_txt,OBJ_TEXT,0,first_sup_time,first_sup_price); ObjectSetString(chart_id,first_low_txt,OBJPROP_TEXT,"1"); second_low_txt = StringFormat("SECOND LOW%d",i); ObjectCreate(chart_id,second_low_txt,OBJ_TEXT,0,second_sup_time,second_sup_price); ObjectSetString(chart_id,second_low_txt,OBJPROP_TEXT,"2"); break; } } } } } }
出力

説明
2番目のスイングローを探す前に、最初のスイングローの時刻と価格を記録し、視覚的に識別しやすくするために、両方のスイングローをチャート上にマークします。その後、forループを使用して、最初のスイングローの後に形成される2番目のスイングローを探索します。ループは、最初のスイングローの1本後、つまり「i + 1」のローソク足から開始され、残りのバーすべてを対象にチェックをおこないます。ループ内では、スイングロー判定関数を用いて、各ローソク足が新たなスイングローのパターンを形成しているかどうかを評価します。
同時に、検出された2番目のスイングローの最安値が、最初のサポートゾーン内、すなわち最初のスイングローの最安値とローソク足実体の下限値の間に収まっているかどうかを判定します。これらの条件が満たされた場合、プログラムは新たに検出されたスイングローの価格と時刻を記録し、サポートゾーン内に正当な第2スイングローが形成されたことを確認します。
2つのスイングローが同一の価格帯を支持している場合にのみサポートゾーンを描画するため、これまでに作成していた矩形オブジェクトは、このループ内に移動する必要があります。これにより、サポートゾーンが早すぎる段階で描画されることを防ぎ、検証済みのサポートレベルのみが表示されるようになります。
サポートゾーンのブレイクアウトの特定
これまでに検出した2つのスイングローは、同じ価格帯付近で反転しており、潜在的なサポートゾーンを形成しているように見えます。しかし、2回の反転があったすべてのゾーンが、この手法に適しているわけではありません。市場がその価格帯を2度尊重し、一度も下抜けしていないゾーンのみを採用する必要があります。そのため、過去にすでにブレイクアウト(下方向へのブレイク)が発生したゾーンは除外しなければなりません。このフィルタリングをおこなうことで、価格が明確に支持および拒否を示している、強固で現在も有効なサポートゾーンのみにEAの焦点を絞ることができます。その結果、将来の売買シグナルの精度が向上します。
例double second_sup_price; datetime second_sup_time; string first_low_txt; string second_low_txt; int sup_bars; int sup_min_low_index; double sup_min_low_price;
if(total_symbol_bars >= bars_check) { for(int i = z ; i < bars_check - z; i++) { if(IsSwingLow(low, i, z)) { first_sup_price = low[i]; first_sup_min_body_price = MathMin(close[i], open[i]); first_sup_time = time[i]; for(int j = i+1; j < bars_check - z; j++) { if(IsSwingLow(low, j, z) && low[j] <= first_sup_min_body_price && low[j] >= first_sup_price) { second_sup_price = low[j]; second_sup_time = time[j]; sup_bars = Bars(_Symbol,timeframe,first_sup_time,TimeCurrent()); sup_min_low_index = ArrayMinimum(low,i,sup_bars); sup_min_low_price = low[sup_min_low_index]; if(sup_min_low_price >= first_sup_price) { support_object = StringFormat("SUPPORT %f",first_sup_price); ObjectCreate(chart_id,support_object,OBJ_RECTANGLE,0,first_sup_time,first_sup_price,TimeCurrent(),first_sup_min_body_price); ObjectSetInteger(chart_id,support_object,OBJPROP_COLOR,clrBlue); ObjectSetInteger(chart_id,support_object,OBJPROP_BACK,true); ObjectSetInteger(chart_id,support_object,OBJPROP_FILL,true); first_low_txt = StringFormat("FIRST LOW%d",i); ObjectCreate(chart_id,first_low_txt,OBJ_TEXT,0,first_sup_time,first_sup_price); ObjectSetString(chart_id,first_low_txt,OBJPROP_TEXT,"1"); second_low_txt = StringFormat("SECOND LOW%d",i); ObjectCreate(chart_id,second_low_txt,OBJ_TEXT,0,second_sup_time,second_sup_price); ObjectSetString(chart_id,second_low_txt,OBJPROP_TEXT,"2"); } break; } } } } }
出力

説明
指定されたサポートゾーンが現在も有効であるか、あるいは市場がそのゾーンを下抜けたかを判断するために、このセクションでは3つの変数を導入します。最初の変数は、最初のスイングローが発生した時刻から現在までに形成されたローソク足の本数をカウントします。これにより、サポートゾーンの有効性を確認、またはブレイクアウトの有無を検出するために調査すべきバーの範囲が定義されます。
次に、プログラムはその範囲内において最も安値を記録したローソク足を特定します。これは、最初のスイングロー以降で最も下方向への動きが大きかったポイントを示します。その後、その最安値における実際の価格値を取得し、後続の判定のために保存します。
最後に、市場がサポートゾーンを下抜けたか、それともその水準を維持しているかを判断する条件が適用されます。検出された最安値が最初のスイングローの価格以上である場合、サポートゾーンは有効であり、市場はその水準を尊重していると判断されます。この場合、矩形、ライン、ラベルなどのグラフィカルオブジェクトを描画することで、サポートゾーンがチャート上に視覚的に表示されます。一方、最安値が元のサポートレベルを下回った場合は、ブレイクアウトが発生したと判断されます。この場合、サポートゾーンはもはや有効ではないため、いかなるグラフィカルオブジェクトも表示されません。
強気CHoCHの特定
有効なサポートゾーンが確認された後、次のステップは強気のCHoCHを検出することです。市場はこのサポートゾーンで2度反発しており、買いの動きが示唆されています。また、この時点で2つのスイングローによってすでにゾーンが定義されています。しかし、サポートゾーンが2度機能しただけでは、強気反転の可能性を確定するには不十分であり、市場構造における明確な変化を確認する必要があります。
そのため、価格が再びサポートゾーンへ戻ってくるのを待ちます。価格がゾーンに再接近した時点で、強気のCHoCHパターンの探索を開始します。このパターンは、市場の勢いが弱気から強気へ転換したことを示します。このケースでは、高値 → 安値 → 高値切り下げ(Lower High) → 安値切り下げ(Lower Low)といった一連の市場構造ポイントが形成される必要があります。安値切り下げが形成された後、ローソク足が直近の高値切り下げラインを上抜けて確定する動きを監視します。このブレイクアウトは、相場の方向転換を示し、買い手が主導権を握ったことを裏付けます。
取引の実行は、CHoCHが検出された下位時間足でおこなわれます。下位時間足は、上位時間足で最初に特定されたサポートおよびレジスタンスゾーンの内部、またはその近辺で、強気のCHoCHを検出することでエントリーの確認をおこなうために使用されます。この多時間軸手法により、精度が高く、かつタイミングの良いエントリーが可能となります。
安値切り下げと高値切り下げの特定
CHoCHの中でも最新の構造変化を特定したいため、分析はチャート上の最新のバーから開始します。これは、価格が新たな安値を更新した最も直近のポイント、すなわち安値切り下げを最初に特定することを意味します。その後、高値切り下げを特定し、次に安値、最後に高値の順で、市場構造の各ポイントを確認していきます。
例
input ENUM_TIMEFRAMES timeframe = PERIOD_M30; //SUPPORT AND RESISTANCE TIMEFRAME input ENUM_TIMEFRAMES exe_timeframe = PERIOD_M5; //EXECUTION TIMEFRAME
double exe_open[]; double exe_close[]; double exe_low[]; double exe_high[]; datetime exe_time[]; int exe_total_symbol_bars; double lower_high; datetime lower_high_time; double lower_low; datetime lower_low_time; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(exe_close,true); ArraySetAsSeries(exe_open,true); ArraySetAsSeries(exe_high,true); ArraySetAsSeries(exe_low,true); ArraySetAsSeries(exe_time,true); //--- return(INIT_SUCCEEDED); }
CopyOpen(_Symbol, exe_timeframe, TimeCurrent(), bars_check, exe_open); CopyClose(_Symbol, exe_timeframe, TimeCurrent(), bars_check, exe_close); CopyLow(_Symbol, exe_timeframe, TimeCurrent(), bars_check, exe_low); CopyHigh(_Symbol, exe_timeframe, TimeCurrent(), bars_check, exe_high); CopyTime(_Symbol, exe_timeframe, TimeCurrent(), bars_check, exe_time);
if(total_symbol_bars >= bars_check) { for(int i = z ; i < bars_check - z; i++) { if(IsSwingLow(low, i, z)) { first_sup_price = low[i]; first_sup_min_body_price = MathMin(close[i], open[i]); first_sup_time = time[i]; for(int j = i+1; j < bars_check - z; j++) { if(IsSwingLow(low, j, z) && low[j] <= first_sup_min_body_price && low[j] >= first_sup_price) { second_sup_price = low[j]; second_sup_time = time[j]; sup_bars = Bars(_Symbol,timeframe,first_sup_time,TimeCurrent()); sup_min_low_index = ArrayMinimum(low,i,sup_bars); sup_min_low_price = low[sup_min_low_index]; if(sup_min_low_price >= first_sup_price) { support_object = StringFormat("SUPPORT %f",first_sup_price); ObjectCreate(chart_id,support_object,OBJ_RECTANGLE,0,first_sup_time,first_sup_price,TimeCurrent(),first_sup_min_body_price); ObjectSetInteger(chart_id,support_object,OBJPROP_COLOR,clrBlue); ObjectSetInteger(chart_id,support_object,OBJPROP_BACK,true); ObjectSetInteger(chart_id,support_object,OBJPROP_FILL,true); first_low_txt = StringFormat("FIRST LOW%d",i); ObjectCreate(chart_id,first_low_txt,OBJ_TEXT,0,first_sup_time,first_sup_price); ObjectSetString(chart_id,first_low_txt,OBJPROP_TEXT,"1"); second_low_txt = StringFormat("SECOND LOW%d",i); ObjectCreate(chart_id,second_low_txt,OBJ_TEXT,0,second_sup_time,second_sup_price); ObjectSetString(chart_id,second_low_txt,OBJPROP_TEXT,"2"); exe_total_symbol_bars = Bars(_Symbol, exe_timeframe); if(exe_total_symbol_bars >= bars_check) { for(int k = 4; k < bars_check-3; k++) { if(IsSwingLow(exe_low, k, 3)) { lower_low = exe_low[k]; lower_low_time = exe_time[k]; for(int l = k; l < bars_check-3; l++) { if(IsSwingHigh(exe_high,l,3)) { lower_high = exe_high[l]; lower_high_time = exe_time[l]; break; } } break; } } } } break; } } } } }
説明
このプログラム領域では、CHoCHに基づいて取引をおこなうためのデータ準備および分析が主な目的となります。まず、実行用の時間足を定義します。この例では5分足のよう下位時間足が使用されます。サポートおよびレジスタンスゾーンは日足や週足といった上位時間足で特定されますが、実際のエントリー確認は、より正確な市場の動きを捉えるために下位時間足でおこなわれます。
次に、実行時間足におけるローソク足の価格データおよび時間データを格納するための配列を作成します。これらの配列は、各ローソク足の高値、安値、始値、終値を正確に参照できるため、市場構造の分析において不可欠です。直近の高値切り下げおよび安値切り下げの価格と時刻に加えて、アクセス可能なローソク足の総数を保持するための他の変数が提供されます。
プログラムは処理を進める前に、選択された時間足に十分な本数のローソク足が存在するかを確認します。条件が満たされると、各ローソク足を順に反復処理し、スイングローの検出を行います。現在のローソク足の安値が、その前後の複数本のローソク足の安値よりも低い場合、そのローソク足はスイングローと判定され、市場における局所的な底を示します。スイングローが検出されると、その価格および時刻が安値切り下げとして記録されます。
安値切り下げが特定された後、プログラムは次にスイングハイの検出へと進みます。ローソク足の高値が隣接するローソク足の高値を上回った場合、そのローソク足は一時的な天井を示すスイングハイと判定されます。安値切り下げと高値切り下げの両方が特定されると、初期分析フェーズは完了し、EAは弱気または強気のCHoCHを評価する準備が整います。
新たな安値および高値の特定
安値切り下げと高値切り下げを特定した後、次の段階では、それらに続いて形成される新たな安値および新たな高値を検出します。このフェーズは、市場構造が正しく形成されているかを確認し、正当なCHoCHを検証するために重要です。高値切り下げが安値切り下げの上に位置していた局面では、市場はそれ以前に下落傾向にありました。
その後に形成される新たな安値は、弱気の勢いが弱まっていることを示すため、高値切り下げより下であり、かつ安値切り下げより上に位置する必要があります。さらに、その後に形成される高値が、それまでのすべての高値および安値を上回る場合、この高値は高値切り上げとなり、市場のモメンタムが弱気から強気へと転換したことを示します。これは、上昇トレンドが始まる可能性を示唆する重要なシグナルとなります。
例
double low_l; datetime low_time; double high_h; datetime high_time;
for(int m = l; m < bars_check-3; m++) { if(IsSwingLow(exe_low, m, 3)) { low_l = exe_low[m]; low_time = exe_time[m]; for(int n = m; n < bars_check-3; n++) { if(IsSwingHigh(exe_high,n,3)) { high_h = exe_high[n]; high_time = exe_time[n]; break; } } break; }
説明
ソフトウェアのこのセクションでは、強気のCHoCHを裏付ける次の重要なスイングポイントを特定するために、複数の条件が使用されます。これには、新たに形成されたスイングローおよびスイングハイの価格と時刻が含まれます。これらのパラメータを監視することで、EAは市場構造が弱気から強気へと転換したかどうかを判断できます。
プログラムは、まず直近のローソク足を調査し、新たなスイングローを検出します。新しい安値が確認されると、すぐにその後に形成される次のスイングハイを探索し、その価格および時刻を記録します。この新たなスイングハイを特定し、その詳細を保存することで、市場構造の変化を評価するために必要な重要なスイングシーケンスの特定が完了します。
その後、以前に形成された高値切り下げを上抜けて陽線が確定した場合、強気のCHoCHが確認されます。この出来事は、買い手が主導権を握ったことを示し、弱気相場の局面が実質的に終了したことを意味します。市場が高値切り上げおよび安値切り上げを形成し始めることで、トレンド反転の可能性と新たな強気構造の始まりが示唆されます。
例
datetime start_choch_time;if(total_symbol_bars >= bars_check) { for(int i = z ; i < bars_check - z; i++) { if(IsSwingLow(low, i, z)) { first_sup_price = low[i]; first_sup_min_body_price = MathMin(close[i], open[i]); first_sup_time = time[i]; for(int j = i+1; j < bars_check - z; j++) { if(IsSwingLow(low, j, z) && low[j] <= first_sup_min_body_price && low[j] >= first_sup_price) { second_sup_price = low[j]; second_sup_time = time[j]; start_choch_time = time[j+z]; sup_bars = Bars(_Symbol,timeframe,first_sup_time,TimeCurrent()); sup_min_low_index = ArrayMinimum(low,i,sup_bars); sup_min_low_price = low[sup_min_low_index]; if(sup_min_low_price >= first_sup_price) { support_object = StringFormat("SUPPORT %f",first_sup_price); ObjectCreate(chart_id,support_object,OBJ_RECTANGLE,0,first_sup_time,first_sup_price,TimeCurrent(),first_sup_min_body_price); ObjectSetInteger(chart_id,support_object,OBJPROP_COLOR,clrBlue); ObjectSetInteger(chart_id,support_object,OBJPROP_BACK,true); ObjectSetInteger(chart_id,support_object,OBJPROP_FILL,true); first_low_txt = StringFormat("FIRST LOW%d",i); ObjectCreate(chart_id,first_low_txt,OBJ_TEXT,0,first_sup_time,first_sup_price); ObjectSetString(chart_id,first_low_txt,OBJPROP_TEXT,"1"); second_low_txt = StringFormat("SECOND LOW%d",i); ObjectCreate(chart_id,second_low_txt,OBJ_TEXT,0,second_sup_time,second_sup_price); ObjectSetString(chart_id,second_low_txt,OBJPROP_TEXT,"2"); exe_total_symbol_bars = Bars(_Symbol, exe_timeframe); if(exe_total_symbol_bars >= bars_check) { for(int k = 4; k < bars_check-3; k++) { if(IsSwingLow(exe_low, k, 3)) { lower_low = exe_low[k]; lower_low_time = exe_time[k]; for(int l = k; l < bars_check-3; l++) { if(IsSwingHigh(exe_high,l,3)) { lower_high = exe_high[l]; lower_high_time = exe_time[l]; for(int m = l; m < bars_check-3; m++) { if(IsSwingLow(exe_low, m, 3)) { low_l = exe_low[m]; low_time = exe_time[m]; for(int n = m; n < bars_check-3; n++) { if(IsSwingHigh(exe_high,n,3)) { high_h = exe_high[n]; high_time = exe_time[n]; for(int o = k; o > 0; o--) { if(exe_close[o] > lower_high && exe_open[o] < lower_high) { if(lower_high > lower_low && low_l < lower_high && low_l > lower_low && high_h > low_l && high_h > lower_high && lower_low <= first_sup_min_body_price && lower_low >= first_sup_price && high_time > start_choch_time) { ObjectCreate(chart_id,"LLLH",OBJ_TREND,0,lower_low_time,lower_low,lower_high_time,lower_high); ObjectSetInteger(chart_id,"LLLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LLLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LHL",OBJ_TREND,0,lower_high_time,lower_high,low_time,low_l); ObjectSetInteger(chart_id,"LHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LH",OBJ_TREND,0,low_time,low_l,high_time,high_h); ObjectSetInteger(chart_id,"LH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"S Cross Line",OBJ_TREND,0,lower_high_time,lower_high,exe_time[o],lower_high); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_WIDTH,2); } break; } } break; } } break; } } break; } } break; } } } } break; } } } } }

説明
この部分ではまず、CHoCH分析を開始する適切なタイミングを示す基準点を設定します。これにより、最新のサポートゾーンが検証された後にのみ、プログラムが市場構造の変化を探索することが保証されます。
次に、プログラムはローソク足を逆方向(最新から過去へ)に反復処理し、高値切り下げを上抜けて確定したローソク足を特定します。始値が高値切り下げの下にあり、終値がその上で確定しているローソク足は、明確なブレイクアウトと市場反転の可能性を示す強いシグナルとなります。
このようなローソク足が見つかると、プログラムはCHoCHが正当であることを確認するために、複数の検証条件を実行します。すべての条件が満たされた後、EAは主要なスイングポイント(安値切り下げ、高値切り下げ、安値切り上げ、高値切り上げ)をチャート上で接続し、市場構造を視覚的に表現します。さらに、高値切り下げを上抜けて確定したブレイクアウトローソク足を強調表示し、買い手が市場の主導権を取り戻したことを示します。このローソク足は、強気のCHoCHを最終的に確定させるシグナルとして機能します。
取引の実行
ロジックの最終セクションである取引実行では、これまでに得られたすべての確認条件が統合され、実際の取引セットアップが構築されます。コードは、強気のCHoCHに必要なすべての条件が満たされているかどうかを最終的に判断します。条件がすべて確認されると、ソフトウェアは市場構造をチャート上に表示し、取引を実行する準備に入ります。
例
#include <Trade/Trade.mqh>
CTrade trade;
input ENUM_TIMEFRAMES timeframe = PERIOD_W1; //SUPPORT AND RESISTANCE TIMEFRAME input ENUM_TIMEFRAMES exe_timeframe = PERIOD_M30; //EXECUTION TIMEFRAME input double lot_size = 0.2; // Lot Size input double RRR = 3; //RRR double ask_price; double take_profit; datetime lastTradeBarTime = 0;
datetime currentBarTime = iTime(_Symbol, exe_timeframe, 0); ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
for(int o = k; o > 0; o--) { if(exe_close[o] > lower_high && exe_open[o] < lower_high) { if(lower_high > lower_low && low_l < lower_high && low_l > lower_low && high_h > low_l && high_h > lower_high && lower_low <= first_sup_min_body_price && lower_low >= first_sup_price && high_time > start_choch_time) { ObjectCreate(chart_id,"LLLH",OBJ_TREND,0,lower_low_time,lower_low,lower_high_time,lower_high); ObjectSetInteger(chart_id,"LLLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LLLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LHL",OBJ_TREND,0,lower_high_time,lower_high,low_time,low_l); ObjectSetInteger(chart_id,"LHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LH",OBJ_TREND,0,low_time,low_l,high_time,high_h); ObjectSetInteger(chart_id,"LH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"S Cross Line",OBJ_TREND,0,lower_high_time,lower_high,exe_time[o],lower_high); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_WIDTH,2); if(exe_time[1] == exe_time[o] && currentBarTime != lastTradeBarTime) { take_profit = MathAbs(ask_price + ((ask_price - lower_low) * RRR)); trade.Buy(lot_size,_Symbol,ask_price,lower_low,take_profit); lastTradeBarTime = currentBarTime; } } break; } }
出力

説明
強気のCHoCHが確認された後、このセクションでは取引のセットアップと実行手順を説明します。システムがすべての取引条件を満たした場合に自動的に買い注文を発注できるよう、まず取引活動を有効化します。取引のパラメータは、各取引のロットサイズ、サポート/レジスタンス検出および実行の時間足、リスクリワードレシオ(RRR)など、複数の入力によって設定されます。RRRは、リスクに対する目標利益を決定します。
アルゴリズムは、現在の市場価格とローソク足の時刻も監視し、各ローソク足ごとに1回だけ反応するように制御します。これにより、同じローソク足で複数回エントリーすることを防ぎます。 エントリー前に、ローソク足のタイミングやそのバーでの既存ポジションの有無など、すべてのパラメータが条件を満たしているかを確認します。条件が揃うと、RRRに基づき利確レベルを決定し、損切りは直近の安値切り下げに設定され、買いポジションが自動的に建てられます。これにより、エントリーは規律ある、安定した、かつ市場の仕組みに基づく判断として実行されることが保証されます。
レジスタンスゾーンの特定
サポートゾーンの反対にあたるレジスタンスゾーンも特定する必要があります。サポートゾーンがスイングローで決まるのに対し、レジスタンスゾーンはスイングハイを基準に特定されます。言い換えれば、下方向への反発ではなく、上方向への反発が確認されたポイントを探すことになります。
サポートゾーンの判定と同じロジックでレジスタンスゾーンを決定しますが、方向が逆になります。まずスイングハイを特定し、その周囲にゾーンを定義します。次に、同じレジスタンスゾーン内で第2のスイングハイが形成されているかを確認します。価格はこのエリアで2度反発しており、ハイライトされたゾーン内で2番目のスイングハイが形成されれば、レジスタンスゾーンとしての有効性が証明されます。
市場はこのレジスタンスゾーンを上抜けるのが難しく、反転して下落する可能性もあるため、売りのチャンスとなります。ただし、実際に弱気のセットアップが成立するかを確認するには、弱気のキャラクターチェンジを待つ必要があります。つまり、市場が高値切り下げと安値切り下げを形成し始めることが条件となります。この弱気のCHoCHが、レジスタンスゾーンの内部またはその近辺で発生した時、売り手が市場を支配し、下落の可能性があることを示す最終的なシグナルとなります。
例
double first_res_price; double first_res_max_body_price; datetime first_res_time; double second_res_price; datetime second_res_time; string resistance_object; int res_bars; int res_max_high_index; double res_max_high_price; string first_high_txt; string second_high_txt; double higher_high; datetime higher_high_time; double higher_low; datetime higher_low_time;
//RESISTANCE for(int i = z ; i < bars_check - z; i++) { if(IsSwingHigh(high, i, z)) { first_res_price = high[i]; first_res_max_body_price = MathMax(close[i], open[i]); first_res_time = time[i]; for(int j = i+1; j < bars_check - z; j++) { if(IsSwingHigh(high, j, z) && high[j] >= first_res_max_body_price && high[j] <= first_res_price) { second_res_price = high[j]; second_res_time = time[j]; start_choch_time = time[j+z]; resistance_object = StringFormat("RESISTANCE %f",first_res_price); res_bars = Bars(_Symbol,timeframe,first_res_time,TimeCurrent()); res_max_high_index = ArrayMaximum(high,i,res_bars); res_max_high_price = high[res_max_high_index]; if(res_max_high_price <= first_res_price) { ObjectCreate(chart_id,resistance_object,OBJ_RECTANGLE,0,first_res_time,first_res_price,TimeCurrent(),first_res_max_body_price); ObjectSetInteger(chart_id,resistance_object,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,resistance_object,OBJPROP_BACK,true); ObjectSetInteger(chart_id,resistance_object,OBJPROP_FILL,true); first_high_txt = StringFormat("FIRST HIGH%d",i); ObjectCreate(chart_id,first_high_txt,OBJ_TEXT,0,first_res_time,first_res_price); ObjectSetString(chart_id,first_high_txt,OBJPROP_TEXT,"1"); second_high_txt = StringFormat("SECOND HIGH%d",i); ObjectCreate(chart_id,second_high_txt,OBJ_TEXT,0,second_res_time,second_res_price); ObjectSetString(chart_id,second_high_txt,OBJPROP_TEXT,"2"); if(exe_total_symbol_bars >= bars_check) { for(int k = 4; k < bars_check-3; k++) { if(IsSwingHigh(exe_high, k, 3)) { higher_high = exe_high[k]; higher_high_time = exe_time[k]; for(int l = k; l < bars_check-3; l++) { if(IsSwingLow(exe_low,l,3)) { higher_low = exe_low[l]; higher_low_time = exe_time[l]; for(int m = l; m < bars_check-3; m++) { if(IsSwingHigh(exe_high, m, 3)) { high_h = exe_high[m]; high_time = exe_time[m]; for(int n = m; n < bars_check-3; n++) { if(IsSwingLow(exe_low,n,3)) { low_l = exe_low[n]; low_time = exe_time[n]; for(int o = k; o > 0; o--) { if(exe_close[o] < higher_low && exe_open[o] > higher_low) { if(higher_low < higher_high && high_h > higher_low && high_h < higher_high && low_l < high_h && low_l < higher_low && higher_high >= first_res_max_body_price && higher_high <= first_res_price && low_time > start_choch_time) { ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low); ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high_h); ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high_h,low_time,low_l); ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"R Cross Line",OBJ_TREND,0,higher_low_time,higher_low,exe_time[o],higher_low); ObjectSetInteger(chart_id,"R Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"R Cross Line",OBJPROP_WIDTH,2); if(exe_time[1] == exe_time[o] && currentBarTime != lastTradeBarTime) { take_profit = MathAbs(ask_price - ((high_h - ask_price) * RRR)); trade.Sell(lot_size,_Symbol,ask_price,higher_high,take_profit); lastTradeBarTime = currentBarTime; } } break; } } break; } } break; } } break; } } break; } } } } break; } } } }
出力

説明
チャート上の重要なスイングハイをローソク足をループで走査して検出することが、レジスタンスゾーン特定の第一ステップです。最初にスイングハイを見つけ、その高値、始値と終値のうち大きい方、発生時刻を記録します。最初のスイングハイを検出した後、プログラムは同じゾーン内に2番目のスイングハイが形成されているかを確認します。
市場がその価格帯を2度試した場合、そのゾーンはレジスタンスとしての有効性が確認されます。レジスタンスゾーンをチャート上に矩形で描画する前に、プログラムは2つのスイングハイの間で最も高い価格を確認し、いずれのローソク足もゾーンを上抜けていないことを保証します。その後、テキストオブジェクトを使って、ゾーンを構成する1番目および2番目のスイングハイの位置を指定します。
レジスタンスゾーンを特定した後、プログラムはエントリー確認のために弱気のCHoCHを探します。実行用の下位時間足を使い、高値→安値→高値切り下げ→安値切り下げというプライスアクションのシーケンスを確認し、弱気パターンを検出します。重要な高値と安値はトレンドラインで接続して視覚化され、タイムスタンプとともに記録されます。
その後、パターン内の安値切り上げを下抜けて確定するローソク足が発生すると、最後の確認シグナルとなります。EAは、エントリー価格とパターンの高値の距離にRRRを掛けて利確レベルを決定し、この条件がレジスタンスゾーンと一致したときに売り注文を実行します。また、直近の取引実行時刻と現在バーの時刻を比較することで、各ローソク足で1回のみ取引を実行することを保証します。この手法により、レジスタンスゾーンは自動的に特定され、プライスアクションパターンで確認され、すべての条件が満たされた場合にのみ取引が実行される仕組みとなります。
注意
本記事で紹介している戦略は、あくまでプロジェクトベースであり、実践的な例を通してMQL5を学ぶことを目的としています。実際の取引で利益が出ることを保証する手法ではありません。
結論
私たちが作成したEAは、少なくとも2回テストされたスイングハイおよびスイングローを検出することで、重要なサポートおよびレジスタンスゾーンを特定します。その後、下位時間足で強気・弱気のCHoCHを監視し、適切なエントリーポイントを確認します。上位時間足でのゾーン検出と下位時間足でのプライスアクションを組み合わせることで、EAは損切りおよび利確が設定された自動売買を実行できるようになります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20021
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
初心者からエキスパートへ:時間フィルタ付き取引
初心者からエキスパートへ:予測価格経路
MQL5 MVCパラダイムのテーブルのビューコンポーネント:基本グラフィック要素
MQL5入門(第26回):MQL5のAPIとWebRequest関数の習得
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索