English
preview
MQL5での取引戦略の自動化(第37回):ビジュアル指標付きレギュラーRSIダイバージェンス・コンバージェンス検出

MQL5での取引戦略の自動化(第37回):ビジュアル指標付きレギュラーRSIダイバージェンス・コンバージェンス検出

MetaTrader 5トレーディング |
12 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第36回)では、 MetaQuotes Language 5 (MQL5)を用いて需給取引システムを開発しました。このシステムは、レンジ相場(持ち合い)による価格帯から供給ゾーンおよび需要ゾーンを特定し、インパルスムーブでその有効性を検証したうえで、トレンド確認とカスタマイズ可能なリスクパラメータを組み合わせ、リテスト時に取引をおこなうものでした。第37回では、ビジュアル指標付きのレギュラーRSIダイバージェンス・コンバージェンスシステムを開発します。このシステムは、価格スイングと相対力指数(RSI)の値との間に発生するレギュラー強気ダイバージェンスおよび弱気ダイバージェンスを検出し、シグナルに基づいて取引を実行します。取引には任意のリスク管理機能を組み込むことができ、さらに分析を支援するため、チャート上に視覚的な表示もおこないます。本記事では以下のトピックを扱います。

  1. レギュラーRSIダイバージェンス・コンバージェンス戦略の理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

記事を読み終える頃には、レギュラーRSIダイバージェンスを取引するための実用的なMQL5戦略が完成し、自由にカスタマイズできる状態になります。それでは始めましょう。


レギュラーRSIダイバージェンス・コンバージェンス戦略の理解

レギュラーRSIダイバージェンス・コンバージェンス戦略は、値動きとRSIオシレーターの間に発生する不一致を検出することで、潜在的なトレンド反転を見極めることに重点を置いています。RSIはモメンタムを測定する指標です。強気ダイバージェンスの場合、価格はより低い安値を形成する一方で、RSIはより高い安値を作り、弱まる下落モメンタムと上昇への反転の可能性を示します。弱気ダイバージェンスの場合、価格はより高い高値を形成するものの、RSIはより低い高値を示し、上昇モメンタムの衰えと下落への反転の可能性を示唆します。

これらのパターンを検出するために、一定数のバーを用いて確定したスイングポイントを使用します。クリーンなダイバージェンスを確保するために、途中でRSIがダイバージェンスラインを横切らないよう、許容値を設定します。強気ダイバージェンスが確認された場合は、確認バーの終値でロングポジションを取ります。弱気ダイバージェンスが確認された場合は、確認バーの終値でショートポジションを取ります。ポジションは、事前設定の損切り(SL)、利確(TP)、および動的トレーリングストップを含むリスク管理のもとで運用されます。これらの要素を組み込むことで、反転局面を効率的に活用できます。以下に典型的なセットアップ例を示します。

強気ダイバージェンスのセットアップ

レギュラー強気ダイバージェンス

弱気ダイバージェンスのセットアップ

レギュラー弱気ダイバージェンス

この戦略の基本的な流れは、スイングハイとスイングローの強さを確認しながら検出し、指定されたバー範囲と許容幅でダイバージェンスを検証し、ロットサイズやリスクパラメータをカスタマイズ可能な自動売買を実行するとともに、チャート上に色付きラインやラベルなどの視覚的補助を追加して、ダイバージェンスを基にした取引システムを構築することです。簡単に言えば、次のようになります。

戦略設計図


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                        RSI Regular Divergence Convergence EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "RSI Settings"
input int RSI_Period = 14;                          // RSI Period
input ENUM_APPLIED_PRICE RSI_Applied = PRICE_CLOSE; // RSI Applied Price

input group "Swing Settings"
input int Swing_Strength = 5;                       // Bars to confirm swing high/low
input int Min_Bars_Between = 5;                     // Min bars between swings for divergence
input int Max_Bars_Between = 50;                    // Max bars between swings for divergence
input double Tolerance = 0.1;                       // Tolerance for clean divergence check

input group "Trade Settings"
input double Lot_Size = 0.01;                       // Fixed Lot Size
input int Magic_Number = 123456789;                 // Magic Number
input double SL_Pips = 300.0;                       // Stop Loss in Pips (0 to disable)
input double TP_Pips = 300.0;                       // Take Profit in Pips (0 to disable)

input group "Trailing Stop Settings"
input bool Enable_Trailing_Stop = true;             // Enable Trailing Stop
input double Trailing_Stop_Pips = 30.0;             // Trailing Stop in Pips
input double Min_Profit_To_Trail_Pips = 50.0;       // Minimum Profit to Start Trailing in Pips

input group "Visualization"
input color Bull_Color = clrGreen;                  // Bullish Divergence Color
input color Bear_Color = clrRed;                    // Bearish Divergence Color
input color Swing_High_Color = clrRed;              // Color for Swing High Labels
input color Swing_Low_Color = clrGreen;             // Color for Swing Low Labels
input int Line_Width = 2;                           // Divergence Line Width
input ENUM_LINE_STYLE Line_Style = STYLE_SOLID;     // Divergence Line Style
input int Font_Size = 8;                            // Swing Point Font Size

//+------------------------------------------------------------------+
//| Indicator Handles and Trade Object                               |
//+------------------------------------------------------------------+
int RSI_Handle = INVALID_HANDLE;                   //--- RSI indicator handle
CTrade obj_Trade;                                  //--- Trade object for position management

まず、「#include <Trade\Trade.mqh>」を用いてTradeライブラリを読み込み、ポジションや注文を管理する組み込み関数を利用できるようにします。次に、ユーザーがカスタマイズできるさまざまな入力パラメータをカテゴリごとに定義します。「RSI Settings」では、RSIの計算期間を示すRSI_Periodを14に設定し、RSI_AppliedをPRICE_CLOSEに指定して終値を基準に計算するようにします。「Swing Settings」では、スイングハイとスイングローを確認するバー本数を示すSwing_Strengthを5に設定し、Min_Bars_BetweenとMax_Bars_Betweenをそれぞれ5と50にしてダイバージェンス検出の範囲を制限、Toleranceを0.1に設定し、クリーンなダイバージェンス判定時にわずかな許容幅を持たせます。

「Trade Settings」では、固定ロットサイズを示すLot_Sizeを0.01に、EAの取引を識別するためのMagic_Numberを123456789に設定し、損切り距離と利確距離を示すSL_PipsとTP_Pipsをともに300.0に設定します(0の場合は無効化されます)。「Trailing Stop Settings」では、Enable_Trailing_Stopをtrueにして動的ストップを有効化し、Trailing_Stop_Pipsを30.0に、トレーリング開始の利益閾値を示すMin_Profit_To_Trail_Pipsを50.0に設定します。「Visualization」では、強気ダイバージェンス用の色をBull_ColorにclrGreen、弱気ダイバージェンス用をBear_ColorにclrRed、スイングハイラベルをSwing_High_ColorにclrRed、スイングローラベルをSwing_Low_ColorにclrGreenに設定し、ライン幅をLine_Widthに2、ラインスタイルをLine_StyleにSTYLE_SOLID、テキストラベルのフォントサイズをFont_Sizeに8と指定します。

最後に、グローバル変数としてRSIインジケーターの参照を格納するRSI_HandleをINVALID_HANDLEで初期化し、取引操作を扱うobj_TradeをCTradeインスタンスとして宣言します。次に、スイングポイント用のグローバル変数を定義し、初期化する必要があります。

//+------------------------------------------------------------------+
//| Swing Variables                                                  |
//+------------------------------------------------------------------+
double Last_High_Price = 0.0;                      //--- Last swing high price
datetime Last_High_Time = 0;                       //--- Last swing high time
double Prev_High_Price = 0.0;                      //--- Previous swing high price
datetime Prev_High_Time = 0;                       //--- Previous swing high time
double Last_Low_Price = 0.0;                       //--- Last swing low price
datetime Last_Low_Time = 0;                        //--- Last swing low time
double Prev_Low_Price = 0.0;                       //--- Previous swing low price
datetime Prev_Low_Time = 0;                        //--- Previous swing low time
double Last_High_RSI = 0.0;                        //--- Last swing high RSI value
double Prev_High_RSI = 0.0;                        //--- Previous swing high RSI value
double Last_Low_RSI = 0.0;                         //--- Last swing low RSI value
double Prev_Low_RSI = 0.0;                         //--- Previous swing low RSI value

次に、「Swing Variables」セクションでグローバル変数を宣言し、最新および前回のスイングハイとスイングローの情報を格納します。具体的には、最新スイングハイの価格と時刻を格納するLast_High_PriceとLast_High_Time、その前のスイングハイ用にPrev_High_PriceとPrev_High_Timeを用意します。同様にスイングローについては、Last_Low_Price、Last_Low_Time、Prev_Low_Price、Prev_Low_Timeを宣言します。これらをインジケーターのデータと対応付けるために、各スイングハイポイントのRSI値を格納するLast_High_RSIとPrev_High_RSI、各スイングローポイントのRSI値を格納するLast_Low_RSIとPrev_Low_RSIも追加します。これらの変数はすべて初期値を0に設定しておき、実行時に動的に更新し、比較することでダイバージェンスの判定をおこなえるようにします。これで準備は整いました。あとはEAプログラム全体を初期化し、特にRSIインジケーターを初期化して、そのウィンドウを参照できる状態にし、後でチャート上に描画できるようにします。

//+------------------------------------------------------------------+
//| Expert Initialization Function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   RSI_Handle = iRSI(_Symbol, _Period, RSI_Period, RSI_Applied); //--- Create RSI indicator handle
   if (RSI_Handle == INVALID_HANDLE) {             //--- Check if RSI creation failed
      Print("Failed to create RSI indicator");     //--- Log error
      return(INIT_FAILED);                         //--- Return initialization failure
   }
   long chart_id = ChartID();                      //--- Get current chart ID
   string rsi_name = "RSI(" + IntegerToString(RSI_Period) + ")"; //--- Generate RSI indicator name
   int rsi_subwin = ChartWindowFind(chart_id, rsi_name); //--- Find RSI subwindow
   if (rsi_subwin == -1) {                         //--- Check if RSI subwindow not found
      if (!ChartIndicatorAdd(chart_id, 1, RSI_Handle)) { //--- Add RSI to chart subwindow
         Print("Failed to add RSI indicator to chart"); //--- Log error
      }
   }
   obj_Trade.SetExpertMagicNumber(Magic_Number);   //--- Set magic number for trade object
   Print("RSI Divergence EA initialized");         //--- Log initialization success
   return(INIT_SUCCEEDED);                         //--- Return initialization success
}

OnInitイベントハンドラでは、まずiRSIを用いてRSIインジケーターハンドルを作成します。この際、現在の通貨ペア、時間足、RSI期間、適用価格タイプを渡してオシレーターを設定します。その後、RSI_HandleがINVALID_HANDLEかどうかを確認し、無効であればPrintでエラーメッセージを出力し、INIT_FAILEDを返して初期化を中断します。次に、ChartIDで現在のチャートIDを取得し、IntegerToStringを使って期間を文字列に変換し、「RSI(」と組み合わせてRSIインジケーター名を構築します。

ChartWindowFindを用いて、チャートIDと先ほど構築したRSIインジケーター名を渡してチャート内のRSIサブウィンドウを検索します。見つからない場合(rsi_subwin == -1)はChartIndicatorAddでサブウィンドウ1にインジケーターを追加します。追加に失敗した場合はエラーログを出力します。その後、obj_Trade.SetExpertMagicNumber(Magic_Number)を呼び出して取引オブジェクトに一意の識別番号を設定します。最後に成功メッセージを出力し、INIT_SUCCEEDEDを返して初期化が正常に完了したことを確認します。プログラムを初期化すると、次のような状態になります。

初期化

これで、プログラムを初期化してインジケーターを参照可能なサブウィンドウに追加できるようになりました。次に、チャート上でスイングポイントを確認および描画できるようにし、それらを利用してダイバージェンスやコンバージェンスを特定する必要があります。そのためのヘルパー関数を定義していきます。

//+------------------------------------------------------------------+
//| Check for Swing High                                             |
//+------------------------------------------------------------------+
bool CheckSwingHigh(int bar, double& highs[]) {
   if (bar < Swing_Strength || bar + Swing_Strength >= ArraySize(highs)) return false; //--- Return false if bar index out of range for swing strength
   double current = highs[bar];                    //--- Get current high price
   for (int i = 1; i <= Swing_Strength; i++) {     //--- Iterate through adjacent bars
      if (highs[bar - i] >= current || highs[bar + i] >= current) return false; //--- Return false if not a swing high
   }
   return true;                                    //--- Return true if swing high
}

//+------------------------------------------------------------------+
//| Check for Swing Low                                              |
//+------------------------------------------------------------------+
bool CheckSwingLow(int bar, double& lows[]) {
   if (bar < Swing_Strength || bar + Swing_Strength >= ArraySize(lows)) return false; //--- Return false if bar index out of range for swing strength
   double current = lows[bar];                     //--- Get current low price
   for (int i = 1; i <= Swing_Strength; i++) {     //--- Iterate through adjacent bars
      if (lows[bar - i] <= current || lows[bar + i] <= current) return false; //--- Return false if not a swing low
   }
   return true;                                    //--- Return true if swing low
}

CheckSwingHigh関数を定義します。この関数は、整数型のbarインデックスと高値の配列への参照を引数として受け取り、指定したバーにスイングハイが存在するかを判定します。まず、ArraySizeを用いてSwing_Strengthに基づきバーが範囲外かどうかを確認し、範囲外であればfalseを返してインデックスエラーを回避します。その後、現在のバーの高値を取得し、1からSwing_Strengthまでループして、左右の隣接バーに現在のバー以上の高値が存在するかを確認します。存在した場合はfalseを返し、すべて条件を満たした場合はtrueを返してスイングハイであることを確認します。

同様に、CheckSwingLow関数を定義します。構造はCheckSwingHighと同じですが、低値を対象とします。バーが範囲内であることを確認し、現在のバーの安値を取得してループ内で左右の隣接バーに現在のバー以下の安値がないかを検証します。条件を満たした場合のみtrueを返し、スイングローであることを判定します。さらに、クリーンなRSIダイバージェンスを検出する関数も定義できます。

//+------------------------------------------------------------------+
//| Check for Clean Divergence                                       |
//+------------------------------------------------------------------+
bool CleanDivergence(double rsi1, double rsi2, int shift1, int shift2, double& rsi_data[], bool bearish) {
   if (shift1 <= shift2) return false;             //--- Return false if shifts invalid
   for (int b = shift2 + 1; b < shift1; b++) {    //--- Iterate between shifts
      double interp_factor = (double)(b - shift2) / (shift1 - shift2); //--- Calculate interpolation factor
      double interp_rsi = rsi2 + interp_factor * (rsi1 - rsi2); //--- Calculate interpolated RSI
      if (bearish) {                               //--- Check for bearish divergence
         if (rsi_data[b] > interp_rsi + Tolerance) return false; //--- Return false if RSI exceeds line plus tolerance
      } else {                                     //--- Check for bullish divergence
         if (rsi_data[b] < interp_rsi - Tolerance) return false; //--- Return false if RSI below line minus tolerance
      }
   }
   return true;                                    //--- Return true if divergence is clean
}

ここでは、CleanDivergence関数を実装します。この関数は、2つのRSIポイント間のダイバージェンスラインが途中のRSI値によって横切られていないことを確認し、条件違反のないクリーンなパターンであることを確認します。関数は、スイング時のRSI値を示すrsi1とrsi2、バーシフトを示すshift1とshift2(shift1はshift2よりも後のバーであることが前提)、RSIデータ配列への参照rsi_data、さらにダイバージェンスタイプを判別するためのbearishブール値を引数として受け取ります。

まず、バーシフトが有効であるかを検証し、無効であればfalseを返します。その後、「shift2 + 1」から「shift1 - 1」までのバーをループし、正規化された位置を用いてinterp_factorを計算し、rsi1とrsi2間の線形補間値としてinterp_rsiを算出します。弱気ダイバージェンスの場合、途中のrsi_data[b]が「interp_rsi + Tolerance」を超えていないかをチェックし、超えている場合はfalseを返します。強気ダイバージェンスの場合は、rsi_data[b]が「interp_rsi - Tolerance」を下回らないことを確認します。すべてのチェックを通過した場合、trueを返し、ダイバージェンスがクリーンで信頼性の高いシグナルであることを確認します。これで、ダイバージェンスを特定するための関数が揃いました。これらを用いてOnTickイベントハンドラ内で実装できます。まずは弱気ダイバージェンスから始めます。

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime last_time = 0;                //--- Store last processed time
   datetime current_time = iTime(_Symbol, _Period, 0); //--- Get current bar time
   if (current_time == last_time) return;        //--- Exit if bar not new
   last_time = current_time;                     //--- Update last time
   int data_size = 200;                          //--- Set data size for analysis
   double high_data[], low_data[], rsi_data[];   //--- Declare arrays for high, low, RSI data
   datetime time_data[];                         //--- Declare array for time data
   CopyHigh(_Symbol, _Period, 0, data_size, high_data); //--- Copy high prices
   CopyLow(_Symbol, _Period, 0, data_size, low_data); //--- Copy low prices
   CopyTime(_Symbol, _Period, 0, data_size, time_data); //--- Copy time values
   CopyBuffer(RSI_Handle, 0, 0, data_size, rsi_data); //--- Copy RSI values
   ArraySetAsSeries(high_data, true);            //--- Set high data as series
   ArraySetAsSeries(low_data, true);             //--- Set low data as series
   ArraySetAsSeries(time_data, true);            //--- Set time data as series
   ArraySetAsSeries(rsi_data, true);             //--- Set RSI data as series
   long chart_id = ChartID();                    //--- Get current chart ID
   // Find latest swing high
   int last_high_bar = -1, prev_high_bar = -1;   //--- Initialize swing high bars
   for (int b = 1; b < data_size - Swing_Strength; b++) { //--- Iterate through bars
      if (CheckSwingHigh(b, high_data)) {        //--- Check for swing high
         if (last_high_bar == -1) {              //--- Check if first swing high
            last_high_bar = b;                   //--- Set last high bar
         } else {                                //--- Second swing high found
            prev_high_bar = b;                   //--- Set previous high bar
            break;                               //--- Exit loop
         }
      }
   }
   if (last_high_bar > 0 && time_data[last_high_bar] > Last_High_Time) { //--- Check new swing high
      Prev_High_Price = Last_High_Price;          //--- Update previous high price
      Prev_High_Time = Last_High_Time;            //--- Update previous high time
      Last_High_Price = high_data[last_high_bar]; //--- Set last high price
      Last_High_Time = time_data[last_high_bar];  //--- Set last high time
      Prev_High_RSI = Last_High_RSI;              //--- Update previous high RSI
      Last_High_RSI = rsi_data[last_high_bar];    //--- Set last high RSI
      string high_type = "H";                     //--- Set default high type
      if (Prev_High_Price > 0.0) {                //--- Check if previous high exists
         high_type = (Last_High_Price > Prev_High_Price) ? "HH" : "LH"; //--- Set high type
      }
      bool higher_high = Last_High_Price > Prev_High_Price; //--- Check for higher high
      bool lower_rsi_high = Last_High_RSI < Prev_High_RSI; //--- Check for lower RSI high
      int bars_diff = prev_high_bar - last_high_bar; //--- Calculate bars between highs
      bool bear_div = false;                     //--- Initialize bearish divergence flag
      if (Prev_High_Price > 0.0 && higher_high && lower_rsi_high && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check bearish divergence conditions
         if (CleanDivergence(Prev_High_RSI, Last_High_RSI, prev_high_bar, last_high_bar, rsi_data, true)) { //--- Check clean divergence
            bear_div = true;                    //--- Set bearish divergence flag
            // Draw divergence lines
            string line_name = "DivLine_Bear_" + TimeToString(Last_High_Time); //--- Set divergence line name
            ObjectCreate(chart_id, line_name, OBJ_TREND, 0, Prev_High_Time, Prev_High_Price, Last_High_Time, Last_High_Price); //--- Create trend line for price divergence
            ObjectSetInteger(chart_id, line_name, OBJPROP_COLOR, Bear_Color); //--- Set line color
            ObjectSetInteger(chart_id, line_name, OBJPROP_WIDTH, Line_Width); //--- Set line width
            ObjectSetInteger(chart_id, line_name, OBJPROP_STYLE, Line_Style); //--- Set line style
            ObjectSetInteger(chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
            ObjectSetInteger(chart_id, line_name, OBJPROP_BACK, false); //--- Set to foreground
            int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Find RSI subwindow
            if (rsi_window != -1) {              //--- Check if RSI subwindow found
               string rsi_line = "DivLine_RSI_Bear_" + TimeToString(Last_High_Time); //--- Set RSI divergence line name
               ObjectCreate(chart_id, rsi_line, OBJ_TREND, rsi_window, Prev_High_Time, Prev_High_RSI, Last_High_Time, Last_High_RSI); //--- Create trend line for RSI divergence
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_COLOR, Bear_Color); //--- Set line color
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_WIDTH, Line_Width); //--- Set line width
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_STYLE, Line_Style); //--- Set line style
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_RAY, false); //--- Disable ray
               ObjectSetInteger(chart_id, rsi_line, OBJPROP_BACK, false); //--- Set to foreground
            }
         }
      }
      // Draw swing label
      string swing_name = "SwingHigh_" + TimeToString(Last_High_Time); //--- Set swing high label name
      if (ObjectFind(chart_id, swing_name) < 0) { //--- Check if label exists
         ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_High_Time, Last_High_Price); //--- Create swing high label
         ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, " " + high_type + (bear_div ? " Bear Div" : "")); //--- Set label text
         ObjectSetInteger(chart_id, swing_name, OBJPROP_COLOR, Swing_High_Color); //--- Set label color
         ObjectSetInteger(chart_id, swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Set label anchor
         ObjectSetInteger(chart_id, swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Set label font size
      }
      ChartRedraw(chart_id);                    //--- Redraw chart
   }
}

OnTickイベントハンドラでは、毎ティック実行される処理の中で、static変数last_timeを用いて前回処理したバーのタイムスタンプを追跡します。iTimeで現在のバー時間を取得し、前回と同じであれば早期に抜けて同一バーでの重複処理を避け、last_timeを更新します。解析対象としてdata_sizeを200バーに設定していますが、これは任意の値であり、必要に応じて増減可能です。また、high_data、low_data、rsi_data、time_dataの各配列を宣言し、CopyHigh、CopyLow、CopyTime、CopyBufferを用いて直近の高値、安値、タイムスタンプ、RSI値をハンドルから取得します。これらの配列はArraySetAsSeriesで時系列インデックス用に設定し、後でオブジェクト描画するためにChartIDでchart_idを取得します。

最新のスイングハイを検出するため、last_high_barとprev_high_barを-1に初期化し、直近バーから「data_size - Swing_Strength」まで逆ループでCheckSwingHighを呼び出し、直近2つの有効なスイングハイを取得して変数に格納し、2つ目が見つかればループを抜けます。新しいスイングハイがtime_data[last_high_bar]とLast_High_Timeを比較して確認された場合、前回のスイングハイ変数に現在のlast値を代入し、Last_High_Price、Last_High_Time、Last_High_RSIを配列から更新します。high_typeはデフォルトで「H」とし、以前の高値が存在する場合、より高い高値なら「HH」、より低い高値なら「LH」と設定します。

次に弱気ダイバージェンスの条件を評価します。Prev_High_Priceが存在し、価格がより高い高値を形成し、RSIがより低い高値を示し、バー差(prev_high_bar - last_high_bar)がMin_Bars_BetweenとMax_Bars_Betweenの範囲内であれば、CleanDivergenceを呼び出して前回と今回のRSIハイ、バーシフト、rsi_data配列、bearish=trueを渡します。クリーンであればbear_divフラグをtrueに設定し、視覚要素を描画します。まず、メインチャート上にDivLine_Bear_にタイムスタンプを付けたトレンドラインオブジェクトをObjectCreateで作成し、前回と今回のスイングハイを結びます。OBJPROP_COLORにBear_Color、OBJPROP_WIDTHにLine_Width、OBJPROP_STYLEにLine_Styleを設定し、レイを無効化し、ObjectSetIntegerで前景表示を指定します。

次に、ChartWindowFindで期間ベースの名前を用いてRSIサブウィンドウを検索し、見つかった場合は同様にRSIサブウィンドウ上に「DivLine_RSI_Bear_」という名前でダイバージェンスラインを作成し、RSI値を結んで同じプロパティを設定します。最後にスイングラベルについては、SwingHigh_にタイムスタンプを付けたテキストオブジェクトが存在するかObjectFindで確認し、存在しない場合はObjectCreateで高値に作成します。ObjectSetStringでテキストにhigh_typeおよび必要に応じて「Bear Div」を含め、Swing_High_Colorを適用し、ANCHOR_LEFT_LOWERにアンカー、Font_SizeをObjectSetIntegerで設定します。最後にChartRedrawでチャートを更新します。コンパイルすると、次の結果が得られます。

弱気ダイバージェンス

弱気ダイバージェンスを特定して可視化できるようになったので、次はそれを取引に反映し、売りポジションを建てる必要があります。ただし、売りポジションを建てる前に、過剰取引を避けるためにすべてのポジションを決済したい場合があります。もちろん、希望に応じて保持することも可能です。そのためのヘルパー関数を定義する必要があります。

//+------------------------------------------------------------------+
//| Open Buy Position                                                |
//+------------------------------------------------------------------+
void OpenBuy() {
   double ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get current ask price
   double sl = (SL_Pips > 0) ? NormalizeDouble(ask_price - SL_Pips * _Point, _Digits) : 0; //--- Calculate stop loss if enabled
   double tp = (TP_Pips > 0) ? NormalizeDouble(ask_price + TP_Pips * _Point, _Digits) : 0; //--- Calculate take profit if enabled
   if (obj_Trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, Lot_Size, 0, sl, tp)) { //--- Attempt to open buy position
      Print("Buy trade opened on bullish divergence"); //--- Log buy trade open
   } else {                                        //--- Handle open failure
      Print("Failed to open Buy: ", obj_Trade.ResultRetcodeDescription()); //--- Log error
   }
}

//+------------------------------------------------------------------+
//| Open Sell Position                                               |
//+------------------------------------------------------------------+
void OpenSell() {
   double bid_price = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get current bid price
   double sl = (SL_Pips > 0) ? NormalizeDouble(bid_price + SL_Pips * _Point, _Digits) : 0; //--- Calculate stop loss if enabled
   double tp = (TP_Pips > 0) ? NormalizeDouble(bid_price - TP_Pips * _Point, _Digits) : 0; //--- Calculate take profit if enabled
   if (obj_Trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, Lot_Size, 0, sl, tp)) { //--- Attempt to open sell position
      Print("Sell trade opened on bearish divergence"); //--- Log sell trade open
   } else {                                        //--- Handle open failure
      Print("Failed to open Sell: ", obj_Trade.ResultRetcodeDescription()); //--- Log error
   }
}

//+------------------------------------------------------------------+
//| Close All Positions                                              |
//+------------------------------------------------------------------+
void CloseAll() {
   for (int p = PositionsTotal() - 1; p >= 0; p--) { //--- Iterate through positions in reverse
      if (PositionGetTicket(p) > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic_Number) { //--- Check position details
         obj_Trade.PositionClose(PositionGetTicket(p)); //--- Close position
      }
   }
}

今後、買いポジションと売りポジションの両方を建てる必要があるため、それに対応するヘルパー関数をすべて作成します。まず、強気ダイバージェンスを検出した際にロングポジションを建てるためのOpenBuy関数を定義します。現時点では買いシグナルを判定するロジックはまだ定義していませんが、いずれにせよ必要になるため先に作っておきます。まず、SymbolInfoDouble関数で現在のask価格を取得します。その後、SL_Pipsが0より大きい場合は、ストップロスを「ask_price - SL_Pips*_Point」で計算し、銘柄の桁数に合わせてNormalizeDoubleで正規化します。無効の場合は0を設定します。利確についても、TP_Pipsが正の値の場合「ask_price + TP_Pips*_Point」で計算して正規化、無効の場合は0に設定します。次に、obj_Trade.PositionOpenを用いて、銘柄、ORDER_TYPE_BUY、Lot_Size、スリッページなし、計算したslとtpを指定して買いポジションを建てます。成功した場合はPrintでメッセージを出力し、失敗した場合はobj_Trade.ResultRetcodeDescriptionからエラー内容を表示します。

同様に、弱気シグナルに対応する売りポジション用のOpenSell関数を作成します。bid価格をSymbolInfoDouble(_Symbol,SYMBOL_BID)で取得し、SL_Pipsが設定されている場合は「bid_price + SL_Pips*_Point」、TP_Pipsが設定されている場合は「bid_price - TP_Pips*_Point」を計算し、obj_Trade.PositionOpenでORDER_TYPE_SELLとしてポジションを開きます。成功または失敗を同様にログに記録します。

最後に、CloseAll関数を実装して、シグナル発生時に新しいポジションを建てる前に既存ポジションをクローズできるようにします。安全のため、「PositionsTotal() - 1」から0まで逆ループで処理します。各ポジションについて、PositionGetTicket(p)が有効で、_SymbolとPositionGetString(POSITION_SYMBOL)が一致し、Magic_NumberとPositionGetInteger(POSITION_MAGIC)が一致する場合に、obj_Trade.PositionCloseでチケットを指定して決済します。これらの関数を用いることで、シグナルが発生した際にまず既存ポジションを決済し、その後に該当シグナルのポジションを建てる流れを実装できます。弱気ダイバージェンスのシグナルに対しては、これらの関数を以下のように組み込みます。

// Bearish divergence detected - Sell signal
CloseAll();                         //--- Close all open positions
OpenSell();                         //--- Open sell position

コンパイルすると、次の結果が得られます。

ショートポジション

これで弱気ダイバージェンスのシグナルを取引できることが確認できました。次に、強気ダイバージェンスについても同様のロジックを実装する必要があります。以下は、それを実現するために適用したロジックです。

// Find latest swing low
int last_low_bar = -1, prev_low_bar = -1; //--- Initialize swing low bars
for (int b = 1; b < data_size - Swing_Strength; b++) { //--- Iterate through bars
   if (CheckSwingLow(b, low_data)) {      //--- Check for swing low
      if (last_low_bar == -1) {           //--- Check if first swing low
         last_low_bar = b;                //--- Set last low bar
      } else {                            //--- Second swing low found
         prev_low_bar = b;                //--- Set previous low bar
         break;                           //--- Exit loop
      }
   }
}
if (last_low_bar > 0 && time_data[last_low_bar] > Last_Low_Time) { //--- Check new swing low
   Prev_Low_Price = Last_Low_Price;       //--- Update previous low price
   Prev_Low_Time = Last_Low_Time;         //--- Update previous low time
   Last_Low_Price = low_data[last_low_bar]; //--- Set last low price
   Last_Low_Time = time_data[last_low_bar]; //--- Set last low time
   Prev_Low_RSI = Last_Low_RSI;           //--- Update previous low RSI
   Last_Low_RSI = rsi_data[last_low_bar]; //--- Set last low RSI
   string low_type = "L";                 //--- Set default low type
   if (Prev_Low_Price > 0.0) {            //--- Check if previous low exists
      low_type = (Last_Low_Price < Prev_Low_Price) ? "LL" : "HL"; //--- Set low type
   }
   bool lower_low = Last_Low_Price < Prev_Low_Price; //--- Check for lower low
   bool higher_rsi_low = Last_Low_RSI > Prev_Low_RSI; //--- Check for higher RSI low
   int bars_diff = prev_low_bar - last_low_bar; //--- Calculate bars between lows
   bool bull_div = false;                 //--- Initialize bullish divergence flag
   if (Prev_Low_Price > 0.0 && lower_low && higher_rsi_low && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check bullish divergence conditions
      if (CleanDivergence(Prev_Low_RSI, Last_Low_RSI, prev_low_bar, last_low_bar, rsi_data, false)) { //--- Check clean divergence
         bull_div = true;                 //--- Set bullish divergence flag
         // Bullish divergence detected - Buy signal
         CloseAll();                      //--- Close all open positions
         OpenBuy();                       //--- Open buy position
         // Draw divergence lines
         string line_name = "DivLine_Bull_" + TimeToString(Last_Low_Time); //--- Set divergence line name
         ObjectCreate(chart_id, line_name, OBJ_TREND, 0, Prev_Low_Time, Prev_Low_Price, Last_Low_Time, Last_Low_Price); //--- Create trend line for price divergence
         ObjectSetInteger(chart_id, line_name, OBJPROP_COLOR, Bull_Color); //--- Set line color
         ObjectSetInteger(chart_id, line_name, OBJPROP_WIDTH, Line_Width); //--- Set line width
         ObjectSetInteger(chart_id, line_name, OBJPROP_STYLE, Line_Style); //--- Set line style
         ObjectSetInteger(chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
         ObjectSetInteger(chart_id, line_name, OBJPROP_BACK, false); //--- Set to foreground
         int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Find RSI subwindow
         if (rsi_window != -1) {          //--- Check if RSI subwindow found
            string rsi_line = "DivLine_RSI_Bull_" + TimeToString(Last_Low_Time); //--- Set RSI divergence line name
            ObjectCreate(chart_id, rsi_line, OBJ_TREND, rsi_window, Prev_Low_Time, Prev_Low_RSI, Last_Low_Time, Last_Low_RSI); //--- Create trend line for RSI divergence
            ObjectSetInteger(chart_id, rsi_line, OBJPROP_COLOR, Bull_Color); //--- Set line color
            ObjectSetInteger(chart_id, rsi_line, OBJPROP_WIDTH, Line_Width); //--- Set line width
            ObjectSetInteger(chart_id, rsi_line, OBJPROP_STYLE, Line_Style); //--- Set line style
            ObjectSetInteger(chart_id, rsi_line, OBJPROP_RAY, false); //--- Disable ray
            ObjectSetInteger(chart_id, rsi_line, OBJPROP_BACK, false); //--- Set to foreground
         }
      }
   }
   // Draw swing label
   string swing_name = "SwingLow_" + TimeToString(Last_Low_Time); //--- Set swing low label name
   if (ObjectFind(chart_id, swing_name) < 0) { //--- Check if label exists
      ObjectCreate(chart_id, swing_name, OBJ_TEXT, 0, Last_Low_Time, Last_Low_Price); //--- Create swing low label
      ObjectSetString(chart_id, swing_name, OBJPROP_TEXT, " " + low_type + (bull_div ? " Bull Div" : "")); //--- Set label text
      ObjectSetInteger(chart_id, swing_name, OBJPROP_COLOR, Swing_Low_Color); //--- Set label color
      ObjectSetInteger(chart_id, swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Set label anchor
      ObjectSetInteger(chart_id, swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Set label font size
   }
   ChartRedraw(chart_id);                 //--- Redraw chart
}

ここでは、弱気ダイバージェンスのシグナルで使用したロジックと同じものを、条件を反転させて適用しました。理解しやすいようにコメントも追加しています。コンパイルすると、以下の結果が得られます。

強気ダイバージェンスポジション

画像から、強気ダイバージェンスの取引も問題なく実行できることが確認できます。次におこなうべきなのは、トレーリングストップを追加して利益を最適化することです。そのための関数も定義します。これにより、コードをモジュール化して管理しやすくできます。

//+------------------------------------------------------------------+
//| Apply Trailing Stop to Positions                                 |
//+------------------------------------------------------------------+
void ApplyTrailingStop() {
   double point = _Point;                         //--- Get symbol point value
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions in reverse
      if (PositionGetTicket(i) > 0) {             //--- Check valid ticket
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic_Number) { //--- Check symbol and magic
            double sl = PositionGetDouble(POSITION_SL); //--- Get current stop loss
            double tp = PositionGetDouble(POSITION_TP); //--- Get current take profit
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
            ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Get position ticket
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Calculate new stop loss
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Check trailing conditions
                  obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position with new stop loss
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Calculate new stop loss
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Check trailing conditions
                  obj_Trade.PositionModify(ticket, newSL, tp); //--- Modify position with new stop loss
               }
            }
         }
      }
   }
}

ここでは、ApplyTrailingStop関数を実装します。この関数は、ポジションが利益閾値に到達した際にストップロスを動的に調整し、市場が有利に動いた場合に利益を確保するのに役立ちます。まず、pip計算用に_Symbolのポイント値を_Pointで取得してpointに代入します。その後、「PositionsTotal() - 1」から0まで逆ループですべてのポジションを処理し、インデックスのずれによる問題なく決済や修正を安全におこないます。

各ポジションについて、PositionGetTicket(i)が有効で0より大きいチケット番号であれば、PositionGetString(POSITION_SYMBOL)で銘柄が_Symbolと一致するか確認し、PositionGetInteger(POSITION_MAGIC)でMagic_Numberと一致するかもチェックします。次に、PositionGetDouble(POSITION_SL)で現在のsl、PositionGetDouble(POSITION_TP)でtp、PositionGetDouble(POSITION_PRICE_OPEN)でopenPrice、PositionGetInteger(POSITION_TICKET)でticketを取得します。

買いポジション(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)の場合、SymbolInfoDouble(_Symbol,SYMBOL_BID)で取得した現在のbidからTrailing_Stop_Pipspointを引き、NormalizeDoubleで_Digitsに合わせてnewSLを計算します。このnewSLが既存のslより大きく、かつ利益が閾値(Min_Profit_To_Trail_Pips * point)を超えているかを確認します。利益は、SymbolInfoDouble(_Symbol,SYMBOL_BID)で取得した現在のbidからopenPriceを引いた値で計算します。条件を満たした場合、obj_Trade.PositionModifyを用いてticketのポジションを更新し、ストップロスをnewSLに変更、テイクプロフィットはそのままにします。

売りポジション(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)の場合も同様に、SymbolInfoDouble(_Symbol,SYMBOL_ASK)で取得したaskに「Trailing_Stop_Pips * point」を加え、NormalizeDoubleでnewSLを算出します。newSLが現在のslより小さく、利益(openPriceからSymbolInfoDoubleで取得した現在のaskを引いた値)が最小閾値を超える場合に限り、obj_Trade.PositionModifyで更新します。これにより、トレーリングは条件を満たすポジションにのみ適用され、他のポジションには影響しません。これで、この関数をOnTickイベントハンドラ内で呼び出すことで、トレーリングストップによる主要処理を自動で実行できるようになりました。

if (Enable_Trailing_Stop && PositionsTotal() > 0) {               //--- Check if trailing stop enabled
   ApplyTrailingStop();                   //--- Apply trailing stop to positions
}

ポジションがある場合は、上で定義したトレーリングストップ関数を毎ティック呼び出すだけで構いません。これにより、以下の結果が得られます。

トレーリングストップ

トレーリングストップを適用した時点で、EAの主要機能はすべて完成です。次におこなうのは、EAをチャートから削除した際に、チャート上のオブジェクトをすべて削除して散らかさないようにすることです。

//+------------------------------------------------------------------+
//| Expert Deinitialization Function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (RSI_Handle != INVALID_HANDLE) IndicatorRelease(RSI_Handle); //--- Release RSI handle if valid
   ObjectsDeleteAll(0, "DivLine_");                //--- Delete all divergence line objects
   ObjectsDeleteAll(0, "SwingHigh_");              //--- Delete all swing high objects
   ObjectsDeleteAll(0, "SwingLow_");               //--- Delete all swing low objects
   Print("RSI Divergence EA deinitialized");       //--- Log deinitialization
}

OnDeinitイベントハンドラでは、まずRSI_HandleがINVALID_HANDLEでない場合、IndicatorReleaseにRSI_Handleを渡してインジケーターのメモリを解放します。次に、ObjectsDeleteAllを用いてチャート上の関連オブジェクトをすべて削除します。サブウィンドウ0で「DivLine_」接頭辞を指定してダイバージェンスラインを、「SwingHigh_」でスイングハイラベルを、「SwingLow_」でスイングロ―ラベルを削除します。最後にPrintで確認メッセージを出力し、RSI Divergence EAが正常に初期化解除されたことをログに残します。これでプログラムの目的はすべて達成されました。残る作業はプログラムのバックテストであり、それは次のセクションで扱います。


バックテスト

徹底的なバックテストによって、次の結果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

本記事ではMQL5でレギュラーRSIダイバージェンス・コンバージェンスシステムを開発しました。このシステムは、スイングハイとスイングローの強さを確認して強気および弱気のダイバージェンスを検出し、バー範囲と許容幅で正確に検証します。取引は固定ロットで実行され、必要に応じてストップロスやテイクプロフィットをpips単位で設定でき、さらにトレーリングストップによる動的リスク管理にも対応しています。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。

このレギュラーRSIダイバージェンス戦略を用いることで、ダイバージェンスシグナルを効果的に取引できるようになり、今後の取引戦略の最適化にも対応可能です。取引をお楽しみください。

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

MetaTrader 5機械学習の設計図(第4回):金融機械学習パイプラインの隠れた欠陥 - ラベルの同時発生 MetaTrader 5機械学習の設計図(第4回):金融機械学習パイプラインの隠れた欠陥 - ラベルの同時発生
金融機械学習における重大な欠陥であるラベルの同時発生が、過学習や実運用でのパフォーマンス低下を引き起こす問題をどのように修正するかを解説します。トリプルバリア法を使用すると、学習用ラベルが時間的に重複し、ほとんどの機械学習アルゴリズムにおける核心的な独立同分布(IID)の仮定に違反します。本記事では、サンプル重み付けを用いた実践的な解決策を提示します。具体的には、売買シグナル間の時間的重複を定量化し、各観測値が持つ固有情報を反映したサンプル重みを計算し、scikit-learnでこれらの重みを実装することで、より堅牢な分類器を構築する方法を学びます。これらの手法を習得することで、取引モデルの堅牢性、信頼性、収益性を向上させることができます。
MQL5でスマート取引マネージャーを構築する:損益分岐点、トレーリングストップ、部分決済を自動化する MQL5でスマート取引マネージャーを構築する:損益分岐点、トレーリングストップ、部分決済を自動化する
「スマート取引マネージャー」エキスパートアドバイザー(EA)をMQL5で構築し、損益分岐点へのストップロス移動、トレーリングストップ、部分決済などの機能で取引管理を自動化する方法を学びましょう。これは、時間を節約し、取引の一貫性を向上させたいトレーダー向けの、実践的かつステップバイステップのガイドです。
プライスアクション分析ツールキットの開発(第48回):加重バイアスダッシュボードを備えた多時間軸ハーモニー指数 プライスアクション分析ツールキットの開発(第48回):加重バイアスダッシュボードを備えた多時間軸ハーモニー指数
本記事では、「多時間軸ハーモニー指数」を紹介します。これはMetaTrader 5向けの高度なエキスパートアドバイザー(EA)で、複数の時間軸からのトレンドの傾向を加重平均し、EMAによって平滑化したうえで、見やすいチャートパネル型ダッシュボードに表示します。さらに、カスタマイズ可能なアラート機能に加え、強いバイアスの閾値を超えた際には自動で売買シグナルをチャート上に描画します。複数時間軸分析を活用し、市場構造に沿ったエントリーを目指すトレーダーに最適なEAです。
定量的トレンド分析:Pythonで統計情報を収集する 定量的トレンド分析:Pythonで統計情報を収集する
外国為替市場における定量的トレンド分析とは何でしょうか。本記事では、EURUSD通貨ペアにおけるトレンド、その大きさ、分布に関する統計を収集します。利益を生む取引用エキスパートアドバイザー(EA)の開発に、定量的トレンド分析がどのように役立つかも示します。