MQL5での取引戦略の自動化(第38回):傾斜角フィルタ付き隠れRSIダイバージェンス取引
はじめに
前回の記事(第37回)では、MetaQuotes Language 5 (MQL5)でレギュラーRSIダイバージェンスコンバージェンスシステムを開発しました。このシステムは、価格スイングとRSI値の間の強気と弱気のレギュラーダイバージェンスを検出し、シグナルに基づいた取引を実行し、オプションでリスク管理もおこない、さらにチャート上に可視化することで分析を容易にしました。第38回では、傾斜角フィルタを備えた隠れRSIダイバージェンス取引システムを開発します。
このシステムでは、スイングポイントを使って隠れ強気および弱気ダイバージェンスを特定し、バー間隔や許容値を用いてクリーンな判定をおこないます。さらに、価格とRSIの傾き角度でシグナルをフィルタリングし、リスク管理付きで取引をおこないます。加えて、チャート上には角度表示を伴う視覚的マーカーを追加します。本記事では以下のトピックを扱います。
記事を読み終える頃には、隠れRSIダイバージェンスを取引するための実用的なMQL5戦略が完成し、自由にカスタマイズできる状態になります。それでは始めましょう。
隠れ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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
長期取引の最適化:包み足と流動性戦略
MetaTrader 5機械学習の設計図(第5回):逐次ブートストラップ - ラベルのバイアス除去とリターンの向上
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5における市場ポジショニング戦略の体系(第1回):NVIDIAのビットワイズ戦略研究
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索