English
preview
MQL5での取引戦略の自動化(第38回):傾斜角フィルタ付き隠れRSIダイバージェンス取引

MQL5での取引戦略の自動化(第38回):傾斜角フィルタ付き隠れRSIダイバージェンス取引

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

はじめに

前回の記事(第37回)では、MetaQuotes Language 5 (MQL5)でレギュラーRSIダイバージェンスコンバージェンスシステムを開発しました。このシステムは、価格スイングとRSI値の間の強気と弱気のレギュラーダイバージェンスを検出し、シグナルに基づいた取引を実行し、オプションでリスク管理もおこない、さらにチャート上に可視化することで分析を容易にしました。第38回では、傾斜角フィルタを備えた隠れRSIダイバージェンス取引システムを開発します。

このシステムでは、スイングポイントを使って隠れ強気および弱気ダイバージェンスを特定し、バー間隔や許容値を用いてクリーンな判定をおこないます。さらに、価格とRSIの傾き角度でシグナルをフィルタリングし、リスク管理付きで取引をおこないます。加えて、チャート上には角度表示を伴う視覚的マーカーを追加します。本記事では以下のトピックを扱います。

  1. 隠れRSIダイバージェンス戦略の理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

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



隠れRSIダイバージェンス戦略の理解

隠れRSIダイバージェンス戦略は、価格スイングとRSIオシレーターの間の特定の不一致を検出することで、トレンド継続のチャンスを特定することに焦点を当てています。RSIは進行中のトレンドにおけるモメンタムの強さを示すため、隠れ強気ダイバージェンスでは、価格がより高い安値を形成する一方でRSIがより低い安値を形成し、弱気の押し目が弱まって上昇トレンドが再開する可能性を示唆します。隠れ弱気ダイバージェンスでは、価格がより低い高値を作る一方でRSIはより高い高値を示し、強気の調整が衰えて下降トレンドが継続する可能性があることを示します。

信頼性を高めるために、価格とRSIの傾き角度でダイバージェンスをフィルタリングし、十分な急勾配または平坦さを確認します。また、指定したバー間隔内でクリーンなパターンを保つための許容値を設定し、その条件を満たした場合に取引を実行します。隠れ強気シグナルで買い、隠れ弱気シグナルで売る形で、ストップロス、テイクプロフィット、トレーリングストップなどのリスクパラメータも定義します。これらの要素を組み合わせることで、確率の高いトレンド継続のセットアップを狙うことが可能です。以下に想定される各セットアップの例を示します。

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

隠れ強気RSIダイバージェンス

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

隠れ弱気RSIダイバージェンス

私たちの計画は、スイングハイとスイングローを指定した強度に基づいて検出し、指定したバー幅と許容範囲内でクリーンな判定をおこない、隠れダイバージェンスを検証することです。さらに、価格とRSIの傾き角度フィルタでシグナルの質を高め、ロットサイズやリスク管理をカスタマイズしたうえで自動売買を実行します。チャート上には色付きラインや角度表示付きラベルなどの視覚的補助も加え、隠れダイバージェンス取引のための効果的なシステムを構築します。簡単に言うと、下図のような構成になります。

全体計画


MQL5での実装

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

//+------------------------------------------------------------------+
//|                         RSI Hidden 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 "Price Divergence Filter"
input bool   Use_Price_Slope_Filter   = false;            // Use Slope Angle Filter for Price
input double Price_Min_Slope_Degrees  = 10.0;             // Minimum Slope Angle in Degrees for Price (0 to disable min)
input double Price_Max_Slope_Degrees  = 80.0;             // Maximum Slope Angle in Degrees for Price (90 to disable max)
input group "RSI Divergence Filter"
input bool   Use_RSI_Slope_Filter    = true;              // Use Slope Angle Filter for RSI
input double RSI_Min_Slope_Degrees   = 1.0;               // Minimum Slope Angle in Degrees for RSI (0 to disable min)
input double RSI_Max_Slope_Degrees   = 89.0;              // Maximum Slope Angle in Degrees for RSI (90 to disable max)
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 bool         Mark_Swings_On_Price = true;           // Mark Swing Points on Price Chart
input bool         Mark_Swings_On_RSI   = true;           // Mark Swing Points on RSI
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に設定し、クリーンなダイバージェンス判定時にわずかな許容幅を持たせます。

「Price Divergence Filter」グループでは、Use_Price_Slope_Filterがデフォルトでfalseに設定されており、角度ベースのフィルタリングを任意で有効化できます。Price_Min_Slope_Degreesは10.0、Price_Max_Slope_Degreesは80.0に設定され、許容される傾きの範囲(度単位)を定義します(最小は0で無効化、最大は90)。同様に「RSI Divergence Filter」では、Use_RSI_Slope_Filterがtrueに設定されており、RSI線の傾きに対してRSI_Min_Slope_Degreesを1.0、RSI_Max_Slope_Degreesを89.0に設定しています。他のパラメータは以前のレギュラーバージョンと同一ですが、今回、角度によるフィルタリングオプションが追加されています。

最後に、グローバル変数を宣言します。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 Hidden 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を返し、スイングローであることを判定します。

//+------------------------------------------------------------------+
//| 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
}
//+------------------------------------------------------------------+
//| Calculate Visual Angle                                           |
//+------------------------------------------------------------------+
double CalculateVisualAngle(long chart_id, int sub_window, datetime time1, double val1, datetime time2, double val2) {
   int x1 = 0, y1 = 0, x2 = 0, y2 = 0;                            //--- Initialize pixel coordinates
   bool ok1 = ChartTimePriceToXY(chart_id, sub_window, time1, val1, x1, y1); //--- Convert first point to XY
   bool ok2 = ChartTimePriceToXY(chart_id, sub_window, time2, val2, x2, y2); //--- Convert second point to XY
   if (!ok1 || !ok2 || x1 == x2) return 0.0;                      //--- Return zero if conversion failed or same x
   double dx = (double)(x2 - x1);                                 //--- Calculate delta x
   double dy = (double)(y2 - y1);                                 //--- Calculate delta y
   if (dx == 0.0) return (dy > 0.0 ? -90.0 : 90.0);               //--- Handle vertical line case
   double angle = MathArctan(-dy / dx) * 180.0 / M_PI;            //--- Calculate angle in degrees
   return MathAbs(angle);                                         //--- Return absolute angle
}

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を返し、ダイバージェンスがクリーンで信頼性の高いシグナルであることを確認します。

次に、「CalculateVisualAngle」関数を定義します。これはチャート上の2点間の視覚的な傾き角度(度単位)を計算し、ダイバージェンスのフィルタリングに役立ちます。関数はchart_idでチャート識別子を受け取り、sub_windowでメインウィンドウかサブウィンドウかを指定します。また、座標としてtime1、val1、time2、val2を受け取ります。まずピクセル用の変数x1、y1、x2、y2を0で初期化し、ChartTimePriceToXYを使って両方のポイントの時間と価格をXYピクセルに変換し、成功可否をok1とok2に格納します。変換に失敗した場合やx座標が同じ場合は0.0を返します。それ以外の場合は「dx = x2 - x1」、「dy = y2 - y1」を計算します。垂直線(dx = 0)の場合はdyの符号に応じて-90.0または90.0を返します。そうでなければ、MathArctanで「-dy / dx」を計算し、「180.0 / M_PI」を掛けて度に変換します。最終的にMathAbsで絶対値を取り、正の傾きとして返します。これでダイバージェンスを特定するための関数が揃ったので、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
   int rsi_window = ChartWindowFind(chart_id, "RSI(" + IntegerToString(RSI_Period) + ")"); //--- Find RSI subwindow
   // 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 lower_high = Last_High_Price < Prev_High_Price;        //--- Check for lower high
      bool higher_rsi_high = Last_High_RSI > Prev_High_RSI;       //--- Check for higher RSI high
      int bars_diff = prev_high_bar - last_high_bar;              //--- Calculate bars between highs
      bool hidden_bear_div = false;                               //--- Initialize hidden bearish divergence flag
      double price_angle = 0.0;                                   //--- Initialize price angle
      double rsi_angle = 0.0;                                     //--- Initialize RSI angle
      if (Prev_High_Price > 0.0 && lower_high && higher_rsi_high && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check hidden bearish divergence conditions
         if (CleanDivergence(Prev_High_RSI, Last_High_RSI, prev_high_bar, last_high_bar, rsi_data, true)) { //--- Check clean divergence
            price_angle = CalculateVisualAngle(chart_id, 0, Prev_High_Time, Prev_High_Price, Last_High_Time, Last_High_Price); //--- Calculate price angle
            rsi_angle = CalculateVisualAngle(chart_id, rsi_window, Prev_High_Time, Prev_High_RSI, Last_High_Time, Last_High_RSI); //--- Calculate RSI angle
            if ((!Use_Price_Slope_Filter || (price_angle >= Price_Min_Slope_Degrees && price_angle <= Price_Max_Slope_Degrees)) &&
                (!Use_RSI_Slope_Filter || (rsi_angle >= RSI_Min_Slope_Degrees && rsi_angle <= RSI_Max_Slope_Degrees))) { //--- Check slope filters
               hidden_bear_div = true;                               //--- Set hidden bearish divergence flag
               // Draw divergence lines
               string line_name = "DivLine_HiddenBear_" + 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
               if (rsi_window != -1) {                               //--- Check if RSI subwindow found
                  string rsi_line = "DivLine_RSI_HiddenBear_" + 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 on price if enabled
      if (Mark_Swings_On_Price) {                                    //--- Check if marking swings on price enabled
         string swing_name = "SwingHigh_" + TimeToString(Last_High_Time); //--- Set swing high label name
         if (ObjectFind(chart_id, swing_name) < 0) {                 //--- Check if label exists
            string high_label_text = " " + high_type + (hidden_bear_div ? " Hidden Bear Div " + DoubleToString(price_angle, 1) + "°" : ""); //--- Set label text with angle if divergence
            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_label_text); //--- 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
         }
      }
      // Draw corresponding swing label on RSI if enabled
      if (Mark_Swings_On_RSI && rsi_window != -1) {                  //--- Check if marking swings on RSI enabled and subwindow found
         string rsi_swing_name = "RSI_SwingHigh_" + TimeToString(Last_High_Time); //--- Set RSI swing high label name
         if (ObjectFind(chart_id, rsi_swing_name) < 0) {             //--- Check if label exists
            string high_label_text_rsi = " " + high_type + (hidden_bear_div ? " Hidden Bear Div " + DoubleToString(rsi_angle, 1) + "°" : ""); //--- Set label text with RSI angle if divergence
            ObjectCreate(chart_id, rsi_swing_name, OBJ_TEXT, rsi_window, Last_High_Time, Last_High_RSI); //--- Create RSI swing high label
            ObjectSetString(chart_id, rsi_swing_name, OBJPROP_TEXT, high_label_text_rsi); //--- Set label text
            ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_COLOR, Swing_High_Color); //--- Set label color
            ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Set label anchor
            ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_FONTSIZE, Font_Size); //--- Set label font size
         }
      }
      ChartRedraw(chart_id);                                         //--- Redraw chart
   }
}

新しいティックごとに実行されるOnTick関数では、まずstatic変数last_timeを使って前回処理したバーの時間を追跡します。現在のバー時間はiTime_Symbol_Period、0を渡して取得し、前回と同じ場合は冗長な計算を避けるために早期リターンします。その後last_timeを更新します。分析用にdata_sizeを200バーと設定し、high_data、low_data、rsi_data、time_dataの配列を宣言します。これらの配列はCopyHigh、CopyLow、CopyTime、CopyBufferを使って、それぞれRSI_Handleから直近の高値、安値、タイムスタンプ、RSI値を取得して埋めます。

配列はArraySetAsSeriesで時系列として設定し(直近バーを先頭にした逆順インデックス用)、ChartIDでchart_idを取得します。また、期間フォーマット名に基づきChartWindowFindでRSIサブウィンドウのインデックスを取得します。最新のスイングハイを特定するため、last_high_barとprev_high_barを-1で初期化し、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 (Higher High)またはLH (Lower High)に設定します。次に隠れ弱気ダイバージェンスを判定します。Prev_High_Priceが設定され、価格がLower High、RSIがHigher Highであり、バー差(prev_high_bar - last_high_bar)がMin_Bars_BetweenとMax_Bars_Betweenの範囲内であれば、CleanDivergenceを呼び出して、以前と直近のRSI高値、シフト、rsi_data、true(弱気)を渡します。

条件がクリアされた場合、価格角度price_angleをCalculateVisualAngleでメインチャート(サブウィンドウ0)の高値の時刻と価格で計算し、rsi_angleをRSIサブウィンドウの高値の時刻とRSI値で計算します。傾きフィルタを確認し、Price_Slope_Filterを使わないか、price_angleがPrice_Min_Slope_DegreesとPrice_Max_Slope_Degreesの間にあり、かつUse_RSI_Slope_Filterを使用しrsi_angleがRSI_Min_Slope_DegreesとRSI_Max_Slope_Degreesの間であれば、hidden_bear_divをtrueに設定し、ダイバージェンスラインを描画します。メインチャートにはDivLine_HiddenBear_+タイムスタンプのトレンドラインをObjectCreateで作成し、OBJPROP_COLORをBear_Color、OBJPROP_WIDTHをLine_Width、OBJPROP_STYLEをLine_Styleに設定、レイを無効にしObjectSetIntegerで前面表示にします。rsi_windowが有効であれば、RSIサブウィンドウにも「DivLine_RSI_HiddenBear_」としてRSI高値を結ぶラインを同様に描画します。

価格スイングラベルについては、Mark_Swings_On_Priceがtrueの場合、SwingHigh_+タイムスタンプの既存テキストオブジェクトをObjectFindで確認し、存在しなければObjectCreateで高値位置に作成し、ObjectSetStringでテキストを設定します。テキストにはhigh_typeと「 Hidden Bear Div 」にフォーマット済みのprice_angle(ダイバージェンスがある場合)を含め、Swing_High_Color、ANCHOR_LEFT_LOWER、Font_SizeをObjectSetIntegerで設定します。Mark_Swings_On_RSIがtrueかつサブウィンドウが見つかれば、RSIラベル「RSI_SwingHigh_」も同様にRSI高値位置に作成し、テキスト(ダイバージェンスがある場合はrsi_angle含む)を設定します。色、アンカー、サイズは同じです。最後に、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 hidden 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 hidden 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_Symbolと「SYMBOL_ASK」を渡して現在のask価格を取得します。その後、SL_Pipsが0より大きい場合は、ストップロスを「ask_price - SL_Pips*_Point」で計算し、_Digitsに合わせて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を渡して取得したチケットが0より大きい有効な値であり、PositionGetString(POSITION_SYMBOL)で取得した銘柄が現在の_Symbolと一致し、かつPositionGetInteger(POSITION_MAGIC)で取得したMagic_Numberが期待値と一致する場合、そのチケットに対してobj_Trade.PositionCloseを呼び出してポジションを決済します。次に、すべてのポジションを決済し、それぞれのポジションを新たに建てる関数を追加する必要があります。呼び出し例は以下の通りです。

// Hidden 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 higher_low = Last_Low_Price > Prev_Low_Price;          //--- Check for higher low
   bool lower_rsi_low = Last_Low_RSI < Prev_Low_RSI;           //--- Check for lower RSI low
   int bars_diff = prev_low_bar - last_low_bar;                //--- Calculate bars between lows
   bool hidden_bull_div = false;                               //--- Initialize hidden bullish divergence flag
   double price_angle = 0.0;                                   //--- Initialize price angle
   double rsi_angle = 0.0;                                     //--- Initialize RSI angle
   if (Prev_Low_Price > 0.0 && higher_low && lower_rsi_low && bars_diff >= Min_Bars_Between && bars_diff <= Max_Bars_Between) { //--- Check hidden bullish divergence conditions
      if (CleanDivergence(Prev_Low_RSI, Last_Low_RSI, prev_low_bar, last_low_bar, rsi_data, false)) { //--- Check clean divergence
         price_angle = CalculateVisualAngle(chart_id, 0, Prev_Low_Time, Prev_Low_Price, Last_Low_Time, Last_Low_Price); //--- Calculate price angle
         rsi_angle = CalculateVisualAngle(chart_id, rsi_window, Prev_Low_Time, Prev_Low_RSI, Last_Low_Time, Last_Low_RSI); //--- Calculate RSI angle
         if ((!Use_Price_Slope_Filter || (price_angle >= Price_Min_Slope_Degrees && price_angle <= Price_Max_Slope_Degrees)) &&
             (!Use_RSI_Slope_Filter || (rsi_angle >= RSI_Min_Slope_Degrees && rsi_angle <= RSI_Max_Slope_Degrees))) { //--- Check slope filters
            hidden_bull_div = true;                               //--- Set hidden bullish divergence flag
            // Hidden bullish divergence detected - Buy signal
            CloseAll();                                           //--- Close all open positions
            OpenBuy();                                            //--- Open buy position
            // Draw divergence lines
            string line_name = "DivLine_HiddenBull_" + 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
            if (rsi_window != -1) {                               //--- Check if RSI subwindow found
               string rsi_line = "DivLine_RSI_HiddenBull_" + 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 on price if enabled
   if (Mark_Swings_On_Price) {                                    //--- Check if marking swings on price enabled
      string swing_name = "SwingLow_" + TimeToString(Last_Low_Time); //--- Set swing low label name
      if (ObjectFind(chart_id, swing_name) < 0) {                 //--- Check if label exists
         string low_label_text = " " + low_type + (hidden_bull_div ? " Hidden Bull Div " + DoubleToString(price_angle, 1) + "°" : ""); //--- Set label text with angle if divergence
         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_label_text); //--- 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
      }
   }
   // Draw corresponding swing label on RSI if enabled
   if (Mark_Swings_On_RSI && rsi_window != -1) {                  //--- Check if marking swings on RSI enabled and subwindow found
      string rsi_swing_name = "RSI_SwingLow_" + TimeToString(Last_Low_Time); //--- Set RSI swing low label name
      if (ObjectFind(chart_id, rsi_swing_name) < 0) {             //--- Check if label exists
         string low_label_text_rsi = " " + low_type + (hidden_bull_div ? " Hidden Bull Div " + DoubleToString(rsi_angle, 1) + "°" : ""); //--- Set label text with RSI angle if divergence
         ObjectCreate(chart_id, rsi_swing_name, OBJ_TEXT, rsi_window, Last_Low_Time, Last_Low_RSI); //--- Create RSI swing low label
         ObjectSetString(chart_id, rsi_swing_name, OBJPROP_TEXT, low_label_text_rsi); //--- Set label text
         ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_COLOR, Swing_Low_Color); //--- Set label color
         ObjectSetInteger(chart_id, rsi_swing_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Set label anchor
         ObjectSetInteger(chart_id, rsi_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イベントハンドラ内で呼び出すことで、トレーリングストップによる主要処理を自動で実行できるようになりました。

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (Enable_Trailing_Stop && PositionsTotal() > 0) {            //--- Check if trailing stop enabled
      ApplyTrailingStop();                                        //--- Apply trailing stop to positions
   }
   static datetime last_time = 0;                                 //--- Store last processed time
   datetime current_time = iTime(_Symbol, _Period, 0);            //--- Get current bar time
   
   //--- THE REST OF THE LOGIC

}

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

トレーリングストップの有効化

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

//+------------------------------------------------------------------+
//| Expert Deinitialization Function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (RSI_Handle != INVALID_HANDLE) {                            //--- Check if RSI handle is valid
      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 found
         ChartIndicatorDelete(chart_id, rsi_subwin, rsi_name);    //--- Delete RSI indicator from chart
         Print("RSI indicator removed from chart");               //--- Log removal
      }
      IndicatorRelease(RSI_Handle);                               //--- Release RSI handle
   }
   ObjectsDeleteAll(0, "DivLine_");                               //--- Delete all divergence line objects
   ObjectsDeleteAll(0, "SwingHigh_");                             //--- Delete all swing high objects
   ObjectsDeleteAll(0, "SwingLow_");                              //--- Delete all swing low objects
   ObjectsDeleteAll(0, "RSI_SwingHigh_");                         //--- Delete all RSI swing high objects
   ObjectsDeleteAll(0, "RSI_SwingLow_");                          //--- Delete all RSI swing low objects
   Print("RSI Hidden Divergence EA deinitialized");               //--- Log deinitialization
}

OnDeinitOnDeinitイベントハンドラを定義します。この関数は定数整数型のreasonパラメータを受け取り、エキスパートアドバイザー(EA)がチャートからアンロードされる際のクリーンアップ処理をおこないます。まず、RSI_HandleがINVALID_HANDLEでない場合、ChartIDで現在のチャートIDを取得し、IntegerToStringで変換した期間を組み合わせてRSIインジケーター名「RSI(期間)」を作成します。次にChartWindowFind でチャートIDと名前を指定してRSIのサブウィンドウを探し、見つかれば(rsi_subwin != -1)ChartIndicatorDeleteを使ってチャート上からインジケーターを削除し、削除ログを出力します。その後、IndicatorReleaseでRSIハンドルのリソースを解放します。

続いて、ObjectsDeleteAll関数を複数回呼び出してチャート上のオブジェクトをすべて削除します。まずサブウィンドウ0で「DivLine_」を指定してダイバージェンスラインを削除し、「SwingHigh_」でスイングハイラベル、「SwingLow_」でスイングローラベル、「RSI_SwingHigh_」でRSI高値ラベル、「RSI_SwingLow_」でRSI安値ラベルを削除します。最後にPrintで終了ログを出力し、RSI隠れダイバージェンスEAが正しく終了されたことを確認します。 これでプログラムの目的はすべて達成されました。残る作業はプログラムのバックテストであり、それは次のセクションで扱います。


バックテスト

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

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

本記事では、MQL5で隠れRSIダイバージェンス取引システムを構築しました。このシステムは、強度確認付きのスイングハイとスイングローを用いて隠れ強気および隠れ弱気ダイバージェンスを特定し、バー数範囲と許容範囲によるクリーンチェックで妥当性を検証します。さらに、価格線およびRSI線の傾き角度をカスタマイズ可能なフィルタとして用いることで、シグナル精度を向上させています。取引は固定ロットで実行され、pips指定のストップロスとテイクプロフィット、加えて動的なリスク管理のためのトレーリングストップにも対応しています。また、価格チャートおよびRSIチャートの両方に、色付きトレンドラインや角度表示付きのスイングポイントラベルを描画し、視覚的なフィードバックも提供します。

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

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

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

長期取引の最適化:包み足と流動性戦略 長期取引の最適化:包み足と流動性戦略
高時間足(W1、D1、MN)に基づいて長期的な分析と取引判断をおこなうEAです。このEAは、短期的な値動きに翻弄されることなく、利確目標に到達するまで自分のトレンドの方向性(バイアス)を頻繁に変えずにポジションを保持できる、忍耐強い長期トレーダー向けに設計されています。
MetaTrader 5機械学習の設計図(第5回):逐次ブートストラップ - ラベルのバイアス除去とリターンの向上 MetaTrader 5機械学習の設計図(第5回):逐次ブートストラップ - ラベルのバイアス除去とリターンの向上
逐次ブートストラップは、金融機械学習におけるブートストラップサンプリングを再構築する手法であり、時間的に重複するラベルを積極的に回避することで、より独立性の高い学習サンプル、より鋭い不確実性推定、そしてより堅牢な取引モデルを実現します。この実践ガイドでは、その直感的な考え方を説明し、アルゴリズムを段階的に示し、大規模データセット向けの最適化コードパターンを提供し、シミュレーションおよび実際のバックテストを通じて測定可能な性能向上を実証します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5における市場ポジショニング戦略の体系(第1回):NVIDIAのビットワイズ戦略研究 MQL5における市場ポジショニング戦略の体系(第1回):NVIDIAのビットワイズ戦略研究
これまでの「MQL5ウィザード」シリーズで積み上げてきた取り組みを基盤とし、それをさらに発展させる新連載を開始します。本連載は、システムトレードおよび戦略テストへのアプローチを一段引き上げることを目的としています。単一タイプのポジションのみを保有するように設計されたエキスパートアドバイザーに焦点を当てます。主にロングポジションのみを扱う設計です。市場トレンドを一方向に限定することで、分析が簡素化され、戦略の複雑さが軽減されます。また、特に為替以外の資産を扱う場合には、重要な洞察が得られる可能性があります。したがって本連載では、株式やその他の非為替資産において、このアプローチが有効かどうかを検証していきます。買い専用戦略は、スマートマネーや機関投資家の戦略と相関することが多いため、その実用性を体系的に探究します。