MQL5での取引戦略の自動化(第37回):ビジュアル指標付きレギュラーRSIダイバージェンス・コンバージェンス検出
はじめに
前回の記事(第36回)では、 MetaQuotes Language 5 (MQL5)を用いて需給取引システムを開発しました。このシステムは、レンジ相場(持ち合い)による価格帯から供給ゾーンおよび需要ゾーンを特定し、インパルスムーブでその有効性を検証したうえで、トレンド確認とカスタマイズ可能なリスクパラメータを組み合わせ、リテスト時に取引をおこなうものでした。第37回では、ビジュアル指標付きのレギュラーRSIダイバージェンス・コンバージェンスシステムを開発します。このシステムは、価格スイングと相対力指数(RSI)の値との間に発生するレギュラー強気ダイバージェンスおよび弱気ダイバージェンスを検出し、シグナルに基づいて取引を実行します。取引には任意のリスク管理機能を組み込むことができ、さらに分析を支援するため、チャート上に視覚的な表示もおこないます。本記事では以下のトピックを扱います。
記事を読み終える頃には、レギュラー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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MetaTrader 5機械学習の設計図(第4回):金融機械学習パイプラインの隠れた欠陥 - ラベルの同時発生
MQL5でスマート取引マネージャーを構築する:損益分岐点、トレーリングストップ、部分決済を自動化する
プライスアクション分析ツールキットの開発(第48回):加重バイアスダッシュボードを備えた多時間軸ハーモニー指数
定量的トレンド分析:Pythonで統計情報を収集する
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索