English
preview
MQL5での取引戦略の自動化(第3回):ダイナミック取引管理のためのZone Recovery RSIシステム

MQL5での取引戦略の自動化(第3回):ダイナミック取引管理のためのZone Recovery RSIシステム

MetaTrader 5トレーディング | 22 4月 2025, 10:12
18 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回(第2回)は、MetaQuotes Language 5 (MQL5)をを使って、雲ブレイクアウト戦略を完全に機能するエキスパートアドバイザー(EA)へと変換する方法を解説しました。今回(第3回)は、取引の管理と損失からの動的なリカバリーを目的とした、より高度な戦略であるZone Recovery RSIシステムに焦点を当てます。このシステムは、取引のエントリーシグナルとして相対力指数(RSI)を利用し、初期ポジションと反対方向に市場が動いた場合には、Zone Recoveryメカニズムによってカウンタートレードを実行します。これにより、ドローダウンを抑え、市場環境に柔軟に対応しながら、全体的な収益性の向上を目指します。

リカバリーロジックのコーディング、動的なロットサイズによるポジションの管理、取引エントリーとリカバリーシグナルのためのRSIの利用のプロセスについて説明します。記事を読み終えるころには、Zone Recovery RSIシステムの実装方法に加え、MQL5ストラテジーテスターを用いたパフォーマンステスト、さらにリスク管理とリターンの最適化方法についても、明確な理解が得られるはずです。理解を容易にするために、この記事は次のように構成されています。

  1. 戦略設計と主要概念
  2. MQL5での実装
  3. バックテストとパフォーマンス分析
  4. 結論


戦略設計と主要概念

Zone Recovery RSIシステムは、取引エントリーに相対強度指数(RSI)インジケーターを活用し、不利な価格変動に対処するためのZone Recoveryメカニズムを組み合わせたものです。RSIが特定の閾値をクロスしたときにエントリーシグナルが発生します。一般的には、RSIが30を下回ると「売られすぎ」と判断され買いエントリー、70を上回ると「買われすぎ」とされ売りエントリーがおこなわれます。しかし、このシステムの真価は、損失が発生した場合に、それをリカバリーする能力にあります。そのために用いられるのが、構造化されたZone Recoveryモデルです。

Zone Recoveryシステムは、各取引に対してZone High、Zone Low、Target High、Target Lowの4つの重要な価格レベルを確立します。取引が開始されると、これらのレベルはエントリー価格を基準にして計算されます。買い取引の場合、Zone Lowはエントリー価格より下に、Zone Highはエントリー価格に設定されます。逆に、売り取引の場合、Zone Highはエントリー価格より上に、Zone Lowはエントリー価格に設定されます。市場がZone Low(買いの場合)またはZone High(売りの場合)を超えると、あらかじめ定義された乗数に基づいてロットサイズを増やし、反対方向のカウンタートレードが発動されます。Target HighおよびTarget Lowは、買いと売りポジションの利益確定ポイントを示しており、市場が有利な方向に動いた際に取引が利益で終了することを確実にします。このアプローチにより、ポジションサイズと価格レベルを体系的に調整しながら、リスクを管理しつつ損失のリカバリーを可能にします。以下に、このモデル全体を要約した図を示します。

Zone Recoveryイラスト


MQL5での実装

Zone Recovery取引戦略の理論を理解したところで、次はこの理論を自動化し、MetaTrader 5用のMetaQuotes Language 5 (MQL5)を使って、EAを作成していきましょう。

EAを作成するには、MetaTrader 5端末で[ツール]タブをクリックし、[MetaQuotes言語エディタ]を選択するか、キーボードのF4を押します。または、ツールバーのIDE(統合開発環境)アイコンをクリックすることもできます。これにより、MetaQuotes言語エディタ環境が開き、取引ロボット、テクニカルインジケーター、スクリプト、関数のライブラリを作成できるようになります。MetaEditorを開いたら、ツールバーの[ファイル]タブで[新しいファイル]を選択するか、CTRL+Nキーを押して新規ドキュメントを開きます。または、[ツール]タブの新規アイコンをクリックすることもできます。MQLウィザードのポップアップが表示されます。

ウィザードが表示されたら、[EA(テンプレート)]を選択し、[次へ]をクリックします。EAの一般プロパティで、[名前]フィールドにEAのファイル名を入力します。フォルダが存在しない場合にフォルダを指定または作成するには、EA名の前にバックスラッシュを使用することに注意してください。例えば、ここではデフォルトで「Experts\」となっています。つまり、私たちのEAはExpertsフォルダに作成され、そこで見つけることができます。他のフィールドはごく簡単ですが、ウィザードの一番下にあるリンクから、正確な手順を知ることができます。

新しいEA名

希望するEAファイル名を入力した後、[次へ]をクリックし、[完了]をクリックします。ここまでくれば、あとはコードを書いて戦略をプログラムするだけです。

まず、EAに関するメタデータを定義することから始めます。これには、EA名、著作権情報、MetaQuotes Webサイトへのリンクが含まれます。EAのバージョンも指定し、1.00とします。

//+------------------------------------------------------------------+
//|                                      1. Zone Recovery RSI EA.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

これにより、プログラムをロードするときにシステムメタデータが表示されます。次に、プログラム内で使用するグローバル変数をいくつか追加します。まず、ソースコードの先頭で#includeを使用して、取引インスタンスをインクルードします。これにより、CTradeクラスにアクセスできるようになります。これを使用して、取引オブジェクトを作成します。これは取引を開始するために必要なので、非常に重要です。

#include <Trade/Trade.mqh>
CTrade obj_Trade;

プリプロセッサは、#include<Trade/Trade.mqh>行をファイルTrade.mqhの内容に置き換えます。角括弧は、Trade.mqhファイルが標準ディレクトリ(通常、terminal_installation_directory\MQL5\Include)から取得されることを示します。カレントディレクトリは検索に含まれません。この行はプログラム中のどこにでも配置できますが、通常は、より良いコード構造と参照を容易にするために、すべてのインクルージョンはソースコードの先頭に置かれます。CTradeクラスのobj_Tradeオブジェクトを宣言すると、MQL5開発者のおかげで、そのクラスに含まれるメソッドに簡単にアクセスできるようになります。

CTRADEクラス

その後、取引システムで使用するいくつかの重要なグローバル変数を宣言する必要があります。

// Global variables for RSI logic
int rsiPeriod = 14;                //--- The period used for calculating the RSI indicator.
int rsiHandle;                     //--- Handle for the RSI indicator, used to retrieve RSI values.
double rsiBuffer[];                //--- Array to store the RSI values retrieved from the indicator.
datetime lastBarTime = 0;          //--- Holds the time of the last processed bar to prevent duplicate signals.

シグナル生成を処理するために、相対力指数(RSI)インジケーターのロジックを管理するために必要なグローバル変数を設定します。まず、rsiPeriodを14に定義します。これは、RSIの計算に使用される価格バーの数を決定します。これはテクニカル分析における標準設定であり、買われ過ぎまたは売られ過ぎの市場状況を測定できます。次に、RSIインジケーターの参照であるrsiHandleを作成します。このハンドルを使用すると、MetaTraderプラットフォームからRSI値を要求して取得できるようになり、インジケーターの動きをリアルタイムで追跡できるようになります。

これらのRSI値を保存するには、インジケーターの出力を保持する配列であるrsiBufferを使用します。このバッファを分析して、RSIが30を下回ったとき(潜在的な買いシグナル)や70を超えたとき(潜在的な売りシグナル)などの重要なクロスオーバーポイントを検出します。最後に、直近で処理されたバーの時間を保存するlastBarTimeを導入します。この変数により、バーごとに1つのシグナルのみが処理され、同じバー内で複数の取引がトリガーされるのを防ぎます。その後、リカバリーメカニズムを処理するクラスを定義できます。

// Global ZoneRecovery object
class ZoneRecovery {

//---

};

ここでは、ZoneRecoveryクラスを使用してZone Recoveryシステムを作成します。このクラスは、リカバリープロセスを管理するために必要なすべての変数、関数、およびロジックのコンテナとして機能します。クラスを使用すると、コードを自己完結型オブジェクトに整理して、取引を管理し、リカバリーの進行状況を追跡し、各取引サイクルの重要なレベルを計算することができます。このアプローチにより、複数の取引ポジションを同時に処理するための構造、再利用性、およびスケーラビリティが向上します。クラスには、privateprotectedpublicメンバーの形式で3つのメンバーのカプセル化を含めることができます。まずprivateメンバーを定義しましょう。

private:
   CTrade trade;                    //--- Object to handle trading operations.
   double initialLotSize;           //--- The initial lot size for the first trade.
   double currentLotSize;           //--- The lot size for the current trade in the sequence.
   double zoneSize;                 //--- Distance in points defining the range of the recovery zone.
   double targetSize;               //--- Distance in points defining the target profit range.
   double multiplier;               //--- Multiplier to increase lot size in recovery trades.
   string symbol;                   //--- Symbol for trading (e.g., currency pair).
   ENUM_ORDER_TYPE lastOrderType;   //--- Type of the last executed order (BUY or SELL).
   double lastOrderPrice;           //--- Price at which the last order was executed.
   double zoneHigh;                 //--- Upper boundary of the recovery zone.
   double zoneLow;                  //--- Lower boundary of the recovery zone.
   double zoneTargetHigh;           //--- Upper boundary for target profit range.
   double zoneTargetLow;            //--- Lower boundary for target profit range.
   bool isRecovery;                 //--- Flag indicating whether the recovery process is active.

ここでは、Zone Recoveryプロセスを管理するための重要なデータを格納するZoneRecoveryクラスのprivateメンバー変数を定義します。これらの変数により、戦略の状態を追跡し、リカバリーゾーンの重要なレベルを計算し、取引実行ロジックを管理できます。

取引の実行、変更、終了など、すべての取引操作を処理するために、CTradeオブジェクトを使用します。initialLotSizeは最初の取引のロットサイズを表し、currentLotSizeは後続のリカバリー取引のロットサイズを追跡し、multiplierに基づいて増加します。zoneSizeとtargetSizeは、リカバリーシステムの重要な境界を定義します。具体的には、リカバリーゾーンはzoneHighとzoneLowによって境界が定められ、利益目標はzoneTargetHighとzoneTargetLowによって定義されます。

取引の流れを追跡するために、前回の取引が実行されたlastOrderType(BUYまたはSELL)とlastOrderPriceを保存します。この情報は、市場の動きに応じて将来の取引をどのように位置付けるかを決定するのに役立ちます。symbol変数は使用されている取引商品を識別し、isRecoveryフラグはシステムがリカバリープロセス中であるかどうかを示します。これらの変数をprivateにしておくことで、クラスの内部ロジックのみがそれらを変更できるようになり、システムの計算の整合性と正確性が維持されます。その後は、簡潔にするために、後で呼び出して定義するのではなく、クラスの関数を直接定義できるようになりました。したがって、必要な関数を宣言して後で定義するのではなく、関数を一度宣言して定義するだけです。まず、リカバリーゾーンを計算する関数を定義しましょう。

// Calculate dynamic zones and targets
void CalculateZones() {
   if (lastOrderType == ORDER_TYPE_BUY) {
      zoneHigh = lastOrderPrice;                 //--- Upper boundary starts from the last BUY price.
      zoneLow = zoneHigh - zoneSize;             //--- Lower boundary is calculated by subtracting zone size.
      zoneTargetHigh = zoneHigh + targetSize;    //--- Profit target above the upper boundary.
      zoneTargetLow = zoneLow - targetSize;      //--- Buffer below the lower boundary for recovery trades.
   } else if (lastOrderType == ORDER_TYPE_SELL) {
      zoneLow = lastOrderPrice;                  //--- Lower boundary starts from the last SELL price.
      zoneHigh = zoneLow + zoneSize;             //--- Upper boundary is calculated by adding zone size.
      zoneTargetLow = zoneLow - targetSize;      //--- Buffer below the lower boundary for profit range.
      zoneTargetHigh = zoneHigh + targetSize;    //--- Profit target above the upper boundary.
   }
   Print("Zone recalculated: ZoneHigh=", zoneHigh, ", ZoneLow=", zoneLow, ", TargetHigh=", zoneTargetHigh, ", TargetLow=", zoneTargetLow);
}

ここでは、Zone Recovery戦略の主要レベルを定義する上で重要な役割を果たすCalculateZones関数を設計します。この関数の主な目的は、エントリー、リカバリー、利益の出口ポイントを導く 4 つの重要な境界、zoneHigh、zoneLow、zoneTargetHigh、zoneTargetLowを計算することです。これらの境界は動的であり、最後に実行された注文の種類と価格に基づいて調整されるため、リカバリープロセスを確実に制御できます。

最後の注文がBUYだった場合、zoneHighをBUY注文が実行された価格に設定します。この時点で、zoneHighからzoneSizeを引いてzoneLowを計算し、元の購入価格より下のリカバリー範囲を作成します。利益目標を確立するために、zoneHighにtargetSizeを加算してzoneTargetHighを計算し、zoneTargetLowはzoneLowより同じtargetSizeだけ下に配置されます。この構造により、リカバリー取引を元のBUYエントリーの下に配置して、利益範囲の上限と下限を定義することができます。

最後の注文がSELLだった場合は、ロジックを反転します。ここでは、zoneLowを最後のSELL注文の価格に設定します。次に、zoneLowにzoneSizeを加算してzoneHighを計算し、リカバリー範囲の上限を形成します。利益目標は、zoneTargetLowをzoneLowより低い値として計算し、zoneTargetHighをzoneHighより高く設定することで確立されます。どちらもtargetSizeによって決まります。この設定により、利益確定ゾーンを定義しながら、元のSELLエントリーを上回るリカバリー取引を開始できるようになります。

このプロセスの最後に、BUY取引とSELL取引の両方について、Zone Recovery境界と利益目標を確立しました。デバッグと戦略評価を支援するために、Print関数を使用して、ログにzoneHigh、zoneLow、zoneTargetHigh、zoneTargetLowの値を表示します。そうすれば、取引実行ロジックを処理する別の関数を定義することができます。

// Open a trade based on the given type
bool OpenTrade(ENUM_ORDER_TYPE type) {
   if (type == ORDER_TYPE_BUY) {
      if (trade.Buy(currentLotSize, symbol)) {
         lastOrderType = ORDER_TYPE_BUY;         //--- Mark the last trade as BUY.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price.
         CalculateZones();                       //--- Recalculate zones after placing the trade.
         Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
         isRecovery = true;                      //--- Set recovery state to true after the first trade.
         return true;
      }
   } else if (type == ORDER_TYPE_SELL) {
      if (trade.Sell(currentLotSize, symbol)) {
         lastOrderType = ORDER_TYPE_SELL;        //--- Mark the last trade as SELL.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price.
         CalculateZones();                       //--- Recalculate zones after placing the trade.
         Print(isRecovery ? "RECOVERY SELL order placed" : "INITIAL SELL order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
         isRecovery = true;                      //--- Set recovery state to true after the first trade.
         return true;
      }
   }
   return false;                                 //--- Return false if the trade fails.
}

ここでは、ブール値を返す関数「OpenTrade」を定義します。この関数の目的は、BUY注文を実行するかSELL注文を実行するかに応じて取引を開始することです。まず、要求された注文タイプがBUYかどうかを確認します。BUYである場合は、trade.Buy関数を使用して、現在のロットサイズと指定された銘柄で買いポジションを開こうとします。取引が正常に開始された場合、lastOrderTypeをBUYに設定し、SymbolInfoDouble関数を使用して銘柄の現在の価格を保存し、bid価格を取得します。この価格はポジションを建てた価格を表します。次に、CalculateZones関数を呼び出してリカバリーゾーンを再計算し、新しいポジションに基づいてゾーンレベルを調整します。

次に、これが初期BUYであったかリカバリーBUYであったかを示すメッセージをログに出力します。三項演算子を使用して、isRecoveryフラグがtrueかfalseかを確認します。trueの場合、メッセージにはリカバリー注文であることが示され、そうでない場合は初期注文であることが示されます。その後、isRecoveryフラグをtrueに設定し、後続の取引がリカバリープロセスの一部とみなされることを示します。最後に、関数はtrueを返し、取引が正常におこなわれたことを確認します。

注文タイプがSELLの場合も、同じ手順に従います。同じパラメータでtrade.Sell関数を呼び出してSELLポジションを開こうとし、正常に実行されたらlastOrderPriceを保存し、同じ方法でリカバリーゾーンを調整します。三項演算子を使用してisRecoveryフラグをチェックし、これが初期SELLかリカバリーSELLかを示すメッセージを出力します。次に、isRecoveryフラグがtrueに設定され、関数は取引が正常におこなわれたことを示すためにtrueを返します。何らかの理由で取引が正常に開始されなかった場合、関数はfalseを返し、取引の試行が失敗したことを示します。これらはprivateにする必要がある重要な関数です。その他の関数は問題なくpublicにできます。

public:
   // Constructor
   ZoneRecovery(double initialLot, double zonePts, double targetPts, double lotMultiplier, string _symbol) {
      initialLotSize = initialLot;
      currentLotSize = initialLot;                 //--- Start with the initial lot size.
      zoneSize = zonePts * _Point;                 //--- Convert zone size to points.
      targetSize = targetPts * _Point;             //--- Convert target size to points.
      multiplier = lotMultiplier;
      symbol = _symbol;                            //--- Initialize the trading symbol.
      lastOrderType = ORDER_TYPE_BUY;
      lastOrderPrice = 0.0;                        //--- No trades exist initially.
      isRecovery = false;                          //--- No recovery process active at initialization.
   }

ここでは、コンストラクタを含むZoneRecoveryクラスのpublicセクションを宣言します。コンストラクタは、ZoneRecoveryクラスのオブジェクトが作成されるときに、特定のパラメータを使用してそのオブジェクトを初期化するために使用されます。コンストラクタは、initialLot、zonePts、targetPts、lotMultiplier、_symbolを入力として受け取ります。

まず、initialLotの値をinitialLotSizeとcurrentLotSizeに割り当て、両方が同じ値(最初の取引のロットサイズを表す)で始まることを確認します。次に、zonePts(ポイント単位のゾーン距離)に、銘柄の最小価格変動を表す組み込み定数である_Pointを掛けて、zoneSizeを計算します。同様に、targetSizeは、同じアプローチを使用して「targetPts」(目標利益距離)をポイントに変換することによって計算されます。multiplierはlotMultiplierに設定されており、これは後でリカバリー取引のロットサイズを調整するために使用されます。

次に、どの取引手段が使用されるかを示すために、「symbol」がsymbol変数に割り当てられます。最初の取引がBUY注文であると想定して、lastOrderTypeは最初にORDER_TYPE_BUYに設定されます。まだ取引が実行されていないため、lastOrderPriceは0.0に設定されます。最後に、isRecoveryはfalseに設定され、リカバリープロセスがまだアクティブではないことを示します。このコンストラクタは、ZoneRecoveryオブジェクトが適切に初期化され、取引とリカバリープロセスを管理できるように準備されていることを保証します。次に、外部シグナルに基づいて取引をトリガーする関数を定義します。

// Trigger trade based on external signals
void HandleSignal(ENUM_ORDER_TYPE type) {
   if (lastOrderPrice == 0.0)                   //--- Open the first trade if no trades exist.
      OpenTrade(type);
}

ここでは、実行される取引のタイプ(BUYまたはSELL)を表すENUM_ORDER_TYPE型をパラメータとして受け取る関数「HandleSignal」を定義します。まず、lastOrderPriceが0.0であるかどうかを確認します。0.0は、以前に取引が実行されていないことを示します。この条件がtrueの場合、これは最初に開かれる取引であることを意味するため、OpenTrade関数を呼び出してtypeパラメータを渡します。OpenTrade関数は、受信したシグナルに基づいて、BUY注文またはSELL注文を開くロジックを処理します。以下のロジックを使用してリカバリー取引を開始することで、ゾーンを管理できるようになりました。

// Manage zone recovery positions
void ManageZones() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current BID price.

   // Open recovery trades based on zones
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) {
      currentLotSize *= multiplier;            //--- Increase lot size for recovery.
      OpenTrade(ORDER_TYPE_SELL);              //--- Open a SELL order for recovery.
   } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) {
      currentLotSize *= multiplier;            //--- Increase lot size for recovery.
      OpenTrade(ORDER_TYPE_BUY);               //--- Open a BUY order for recovery.
   }
}

開かれた取引を管理するために、事前定義された価格帯に基づいてリカバリー取引を管理する役割を持つvoid関数「ManageZones」を定義します。この関数内では、まずSYMBOL_BIDパラメータを指定したSymbolInfoDouble関数を使用して、指定された銘柄の現在のBID価格を取得します。これにより、資産が取引されている現在の市場価格がわかります。

次に、lastOrderType変数を使用して、最後に実行された注文の取引タイプを確認します。最後の取引がBUYで、現在の市場価格がzoneLow(リカバリーゾーンの下限)以下に下がった場合、currentLotSizeにmultiplierを掛けて増加し、リカバリー取引にさらに多くの資本を割り当てます。その後、ORDER_TYPE_SELLパラメータを指定してOpenTrade関数を呼び出し、前回のBUY取引による損失を管理するためにSELLポジションを開く必要があることを示します。

同様に、最後の取引がSELLで、現在の市場価格がzoneHigh(リカバリーゾーンの上限)まで上昇した場合、currentLotSizeにmultiplierを掛けて再度増加し、リカバリーのために取引サイズを拡大します。次に、ORDER_TYPE_BUYパラメータを使用してOpenTrade関数を呼び出し、以前のSELL取引からリカバリーするためにBUYポジションを開きます。とても簡単です。さて、初期取引とリカバリー取引を開始した後、ある時点でそれらをクローズするためのロジックが必要です。そこで、クロージャまたはターゲットロジックを以下のように定義しましょう。

// Check and close trades at zone targets
void CheckCloseAtTargets() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current BID price.

   // Close BUY trades at target high
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) {
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Loop through all open positions.
         if (PositionGetSymbol(i) == symbol) { //--- Check if the position belongs to the current symbol.
            ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Retrieve the ticket number.
            int retries = 10;
            while (retries > 0) {
               if (trade.PositionClose(ticket)) { //--- Attempt to close the position.
                  Print("Closed BUY position with ticket: ", ticket);
                  break;
               } else {
                  Print("Failed to close BUY position with ticket: ", ticket, ". Retrying... Error: ", GetLastError());
                  retries--;
                  Sleep(100);                   //--- Wait 100ms before retrying.
               }
            }
            if (retries == 0)
               Print("Gave up on closing BUY position with ticket: ", ticket);
         }
      }
      Reset();                                  //--- Reset the strategy after closing all positions.
   }
   // Close SELL trades at target low
   else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) {
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Loop through all open positions.
         if (PositionGetSymbol(i) == symbol) { //--- Check if the position belongs to the current symbol.
            ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Retrieve the ticket number.
            int retries = 10;
            while (retries > 0) {
               if (trade.PositionClose(ticket)) { //--- Attempt to close the position.
                  Print("Closed SELL position with ticket: ", ticket);
                  break;
               } else {
                  Print("Failed to close SELL position with ticket: ", ticket, ". Retrying... Error: ", GetLastError());
                  retries--;
                  Sleep(100);                   //--- Wait 100ms before retrying.
               }
            }
            if (retries == 0)
               Print("Gave up on closing SELL position with ticket: ", ticket);
         }
      }
      Reset();                                  //--- Reset the strategy after closing all positions.
   }
}

ここでは、未決取引が事前定義された目標価格に達したかどうかを確認し、それに応じて取引を終了する役割を担う関数「CheckCloseAtTargets」を定義します。

まず、SYMBOL_BIDパラメータを指定したSymbolInfoDouble関数を使用して、指定された銘柄の現在のBID価格を取得します。これにより、銘柄の現在の市場価格がわかり、これを使用して目標価格レベル(zoneTargetHighまたはzoneTargetLowのいずれか)と比較して、取引を終了するかどうかを決定します。

次に、最後の注文タイプがBUYであるかどうか、および現在の価格がzoneTargetHigh(BUY取引の目標価格レベル)に達したか、それを上回ったかどうかを確認します。これらの条件が満たされた場合、最後のポジションから始めて、PositionsTotal関数を使用してすべてのポジションをループします。ポジションごとに、PositionGetSymbol関数を使用して、ポジションが同じ銘柄に属しているかどうかを確認します。銘柄が一致する場合、POSITION_TICKETパラメータを指定したPositionGetInteger関数を使用して、ポジションのチケット番号を取得します。

その後、取得したチケットを使用してtrade.PositionClose関数を呼び出してポジションをクローズしようとします。ポジションが正常にクローズされた場合、チケット番号を含む、BUYポジションがクローズされたことを示す確認メッセージが出力されます。クローズに失敗した場合は、最大10回再試行し、そのたびにエラーメッセージを出力し、Sleep関数を使用して再試行前に100ミリ秒待機します。10回再試行してもポジションをクローズできない場合は、失敗メッセージを出力し、次のポジションに進みます。すべてのポジションがクローズされるか、再試行制限に達すると、Reset関数を呼び出して戦略をリセットし、将来の取引のために状態がクリアされるようにします。

同様に、最後の注文タイプがSELLであり、現在の価格がzoneTargetLow(SELL取引の目標価格レベル)に達したかそれを下回った場合、すべてのSELLポジションに対してこのプロセスが繰り返されます。この関数は、同じ方法でSELLポジションのクローズを試行し、必要に応じて再試行し、各ステップでステータスメッセージを出力します。ステータスをリセットするために外部関数を使用しましたが、採用されたロジックは次のとおりです。

// Reset the strategy after hitting targets
void Reset() {
   currentLotSize = initialLotSize;             //--- Reset lot size to the initial value.
   lastOrderType = -1;                          //--- Clear the last order type.
   lastOrderPrice = 0.0;                        //--- Clear the last order price.
   isRecovery = false;                          //--- Set recovery state to false.
   Print("Strategy reset after closing trades.");
}

関数「Reset」を定義します。この関数は、戦略の内部変数をリセットし、次の取引またはリセットシナリオのためにシステムを準備する役割を果たします。まず、currentLotSizeをinitialLotSizeにリセットします。つまり、一連のリカバリー取引または目標レベルへの到達後に、ロットサイズを元の値に戻します。これにより、新しい取引では、戦略が初期ロットサイズで新しく開始されることが保証されます。

次に、lastOrderTypeを-1に設定してクリアします。これは、以前の注文タイプ(BUYでもSELLでもない)がないことを効果的に示します。これにより、将来の取引ロジックで以前の注文タイプに関する混乱や依存が生じないようにすることができます。同様に、lastOrderPriceを0.0にリセットし、取引が実行された最後の価格をクリアします。次に、isRecoveryフラグをfalseに設定し、リカバリープロセスがアクティブでなくなったことを通知します。これは、将来の取引がリカバリー戦略の一部ではなく、初期取引として扱われることを保証するため、特に重要です。

最後に、すべての取引を終了した後に戦略が正常にリセットされたことを示すメッセージをPrint関数を使用して出力します。これにより、端末にフィードバックが提供され、トレーダーは戦略がいつリセットされたかを追跡し、将来の操作のための適切な状態を確保することができます。本質的には、この関数は取引条件、リカバリー状態、取引サイズを追跡するすべての重要な変数をクリアし、システムを新規操作のデフォルト設定に戻します。これが、クラスがすべての受信シグナルを処理するために必要なすべてです。ここで、デフォルトのパラメータを渡してクラスオブジェクトの初期化に進むことができます。

ZoneRecovery zoneRecovery(0.1, 200, 400, 2.0, _Symbol);
//--- Initialize the ZoneRecovery object with specified parameters.

ここでは、コンストラクタを呼び出して必要なパラメータを渡すことで、ZoneRecoveryクラスのインスタンスを作成します。具体的には、オブジェクトZoneRecoveryを次の値で初期化します。

  • 初期ロットサイズ0.1:最初の取引ではロットサイズ0.1が使用されることになります。
  • ゾーンサイズ200:リカバリーゾーンの範囲を定義するポイントの数です。次に、_Pointで乗算して、この値を指定された銘柄の実際のポイントに変換します。
  • 目標サイズ400:目標利益レベルまでの距離をポイントで定義します。ゾーンサイズと同様に、これも_Pointを使用してポイントに変換されます。
  • 乗数2.0:必要に応じて、後続のリカバリー取引でロットサイズを増やすために使用されます。
  • _Symbol:ZoneRecoveryのこの特定のインスタンスの取引銘柄として使用され、トレーダーが使用している金融商品の銘柄に対応します。

これらのパラメータを使用してZoneRecoveryを初期化することにより、リカバリーゾーン、ロットサイズの調整、および開かれるまたは管理されるすべての取引のターゲットレベルの管理を含む、この特定の取引戦略の取引ロジックを処理するオブジェクトを設定します。このオブジェクトは、システムが実行されると、定義されたリカバリー戦略に基づいて取引操作を処理できるようになります。ここで、シグナル生成に集中するイベントハンドラに進むことができます。まず、OnInitイベントハンドラから始めます。ここでは、インジケーターハンドルを初期化し、ストレージ配列を時系列として設定するだけです。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   //--- Initialize RSI indicator
   rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE); //--- Create RSI indicator handle.
   if (rsiHandle == INVALID_HANDLE) { //--- Check if RSI handle creation failed.
      Print("Failed to create RSI handle. Error: ", GetLastError());
      return(INIT_FAILED); //--- Return failure status if RSI initialization fails.
   }
   ArraySetAsSeries(rsiBuffer, true); //--- Set the RSI buffer as a time series to align values.
   Print("Zone Recovery Strategy initialized."); //--- Log successful initialization.
   return(INIT_SUCCEEDED); //--- Return success status.
}

ここでは、OnInit関数で一連のセットアップタスクを実行して、RSIインジケーターを初期化し、システムを取引用に準備します。まず、iRSI関数を呼び出して、現在の銘柄(_Symbol)、PERIOD_CURRENT時間枠、指定されたrsiPeriod、およびPRICE_CLOSE価格タイプを渡して、RSIインジケーターハンドルを作成します。このステップでは、戦略で使用するためにRSIインジケーターを設定します。

次に、ハンドルがINVALID_HANDLEと等しくないかどうかを確認して、ハンドルの作成が成功したかどうかを確認します。作成に失敗した場合は、GetLastError関数を使用して特定のエラーコードを含むエラーメッセージを出力し、失敗を通知するために"INIT_FAILED"を返します。ハンドルの作成が成功したら、ArraySetAsSeriesを使用してRSIバッファを時系列として設定し、バッファをチャートの時系列に合わせて、最新の値がインデックス0にあることを確認します。最後に、Zone Recovery戦略の初期化を確認する成功メッセージを出力し、セットアップが成功し、EAが操作を開始する準備ができていることを示すINIT_SUCCEEDEDを返します。以下の図は、その動作を示したものです。

初期化成功

ただし、インジケーターを作成して初期化するので、プログラムがリソースを必要としなくなったら、インジケーターを解放する必要があります。ここで採用するロジックは次のとおりです。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (rsiHandle != INVALID_HANDLE) //--- Check if RSI handle is valid.
      IndicatorRelease(rsiHandle); //--- Release RSI indicator handle to free resources.

   Print("Zone Recovery Strategy deinitialized."); //--- Log deinitialization message.
}

ここでは、戦略を初期化解除し、EAが削除または停止されたときにRSIインジケーターによって使用されるすべてのリソースを解放します。OnDeinit関数では、まずrsiHandleがINVALID_HANDLEと等しくないことを確認して、有効かどうかを確認します。これにより、RSIインジケータハンドルを解放する前に、そのハンドルが存在することが保証されます。

ハンドルが有効な場合は、IndicatorRelease関数を使用してRSIインジケーターに関連付けられたリソースを解放し、EAの実行が停止した後もメモリが適切に管理され、使用中のままにならないようにします。最後に、「Zone Recovery Strategy deinitialized」というメッセージを出力して、初期化解除プロセスが完了したことをログに記録し、システムが適切にシャットダウンされたことを確認します。これにより、不要なリソースを割り当てたままにすることなく、EAを安全に削除できるようになります。結果の例を以下に示します。

RSIインジケーターハンドルのリリースに成功しました

プログラムが停止するインスタンスを処理した後、ティックが処理されるメインの最終イベントハンドラ、つまりOnTickイベントハンドラに進むことができます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Copy RSI values
   if (CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) <= 0) { //--- Attempt to copy RSI buffer values.
      Print("Failed to copy RSI buffer. Error: ", GetLastError()); //--- Log failure if copying fails.
      return; //--- Exit the function on failure to avoid processing invalid data.
   }

//---

}

OnTick関数では、まずCopyBuffer関数を使用してRSI値をrsiBuffer配列にコピーしようとします。CopyBuffer関数は、RSIインジケーターハンドル「rsiHandle」、バッファインデックス0(プライマリRSIバッファを示す)、開始ポジション1(データのコピーを開始するポジション)、コピーする値の数2、およびコピーされたデータを格納するrsiBuffer配列のパラメータとともに呼び出されます。この関数は、最新の2つのRSI値を取得し、バッファに保存します。

次に、返された値が0より大きいかどうかを評価して、コピー操作が成功したかどうかを確認します。操作が失敗した場合(つまり、0以下の値を返す場合)、Print関数を使用して「RSIバッファコピー」が失敗したことを示すエラーメッセージを記録し、失敗の詳細を示すGetLastErrorコードを表示します。エラーを記録した後、無効または欠落したRSIデータに基づくそれ以上の処理を防止するために、returnを使用してすぐに関数を終了します。これにより、EAが不完全または不完全なデータに基づいて取引の決定を下そうとすることがなくなり、潜在的なエラーや損失を回避できます。プロセスを終了しない場合は、必要な要求されたデータがあり、取引の決定を継続できることを意味します。

//--- Check RSI crossover signals
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar.
if (currentBarTime != lastBarTime) { //--- Ensure processing happens only once per bar.
   lastBarTime = currentBarTime; //--- Update the last processed bar time.
   if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) { //--- Check for RSI crossing below 30 (oversold signal).
      Print("BUY SIGNAL"); //--- Log a BUY signal.
      zoneRecovery.HandleSignal(ORDER_TYPE_BUY); //--- Trigger the Zone Recovery BUY logic.
   } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) { //--- Check for RSI crossing above 70 (overbought signal).
      Print("SELL SIGNAL"); //--- Log a SELL signal.
      zoneRecovery.HandleSignal(ORDER_TYPE_SELL); //--- Trigger the Zone Recovery SELL logic.
   }
}

ここでは、新しい市場バーごとにRSIクロスオーバーシグナルをチェックし、潜在的な取引をトリガーします。まず、iTime関数を使用して現在のバーのタイムスタンプを取得します。この関数は、銘柄(_Symbol)、時間枠(PERIOD_CURRENT)、およびバーインデックス(現在のバーの場合は0)を受け取ります。これは、最後に完了したバーのタイムスタンプを表すcurrentBarTimeを提供します。

次に、currentBarTimeとlastBarTimeを比較して、取引ロジックがバーごとに1回だけ実行されることを確認します。時間が異なっている場合は、新しいバーが形成されたことを意味するので、処理を続行します。次に、lastBarTimeをcurrentBarTimeと一致するように更新して、最後に処理されたバーを追跡し、同じバーでの繰り返し実行を防止します。

次のステップは、RSIクロスオーバーシグナルを検出することです。まず、rsiBuffer[1](前のバーのRSI値)とrsiBuffer[0](現在のバーのRSI値)を比較して、RSI値が30を下回ったかどうか(売られ過ぎの状態)を確認します。前のバーのRSIが30を超えており、現在のバーのRSIが30以下である場合、これは潜在的なBUYシグナルを示しているため、「BUY SIGNAL」というメッセージを出力し、zoneRecoveryオブジェクトのHandleSignal関数を呼び出して、BUY注文のリカバリープロセスをトリガーします。

同様に、RSIが70を超えたかどうか(買われすぎの状態)を確認します。前のバーのRSIが70未満で、現在のバーのRSIが70以上の場合、潜在的なSELLシグナルを示し、「SELL SIGNAL」と出力します。次に、再度HandleSignalを呼び出しますが、今回はSELL注文で、対応するZone RecoverySELLロジックをトリガーします。最後に、開いたゾーンを管理し、ターゲットに到達したらゾーンを閉じるためのそれぞれの関数を呼び出します。

//--- Manage zone recovery logic
zoneRecovery.ManageZones(); //--- Perform zone recovery logic for active positions.

//--- Check and close at zone targets
zoneRecovery.CheckCloseAtTargets(); //--- Evaluate and close trades when target levels are reached.

ここでは、ドット演算子(「.」)を使用して、ZoneRecoveryクラスの一部である関数を呼び出します。まず、zoneRecovery.ManageZones()を使用してManageZonesメソッドを実行します。このメソッドは、現在の価格と定義されたリカバリーゾーンに基づいてZone Recovery取引を管理するためのロジックを処理します。この方法では、リカバリー取引のロットサイズを調整し、必要に応じて新しいポジションを開きます。

次に、zoneRecovery.CheckCloseAtTargets()を呼び出してCheckCloseAtTargetsメソッドをトリガーし、価格がポジションをクローズするための目標レベルに達したかどうかを確認します。条件が満たされた場合、未決済取引を終了し、戦略が目標の利益または損失の境界に沿ったままであることを保証します。ドット演算子を使用することで、zoneRecoveryオブジェクト上のこれらのメソッドにアクセスして実行し、リカバリープロセスを効率的に管理します。メソッドがティックごとに正常に呼び出されることを確認するために、プログラムを実行します。結果は次のようになります。

ONTICKイベントハンドラリセット確認 

画像からは、最初のティックでプログラムを初期化するためのクラスメソッドが正常に呼び出されていることが確認できます。これにより、プログラムクラスが正しく接続され、動作の準備が整っていることがわかります。これを確認するためにプログラムを実行すると、取引の確認が表示されます。

買いのログと確認

画像からは、買いシグナルを確認し、それに基づいてポジションを建て、それをZone Recoveryシステムに追加してゾーンレベルを再計算し、それが初期ポジションであることを識別し、目標に到達した時点でポジションをクローズして、次の取引に向けてシステムをリセットしていることが確認できます。Zone Recoveryシステムに入るケースを試して見てみましょう。 

Zone Recovery開始

画像からは、市場が200ポイントこちらに不利な方向へ動いた場合、トレンドが強気であると判断し、それに従ってロットサイズを増やし(この場合は0.2)、買いポジションを新たに建てていることがわかります。

完全リカバリー

ここでも、市場が目標レベルに達すると、取引を終了して次の取引にリセットすることがわかります。システムがリカバリーモードの間は、すべての着信シグナルを無視します。これにより、目的が正常に達成されたことが証明され、残っているのはプログラムをバックテストしてそのパフォーマンスを分析することです。これについては次のセクションで説明します。


バックテストとパフォーマンス分析

このセクションでは、Zone Recovery RSIシステムのバックテストとパフォーマンス分析のプロセスに焦点を当てます。バックテストをおこなうことで、過去のデータに対する戦略の有効性を検証し、潜在的な課題や欠陥を洗い出し、ライブ取引におけるパフォーマンス向上のためにパラメータを調整することが可能になります。

まず、MetaTrader 5プラットフォームでストラテジーテスターを設定します。ストラテジーテスターを使用することで、過去の市場環境をシミュレートし、あたかもリアルタイムで取引をおこなっているかのようにEAの動作を検証できます。バックテストを実行するには、対象となる銘柄、時間足、テスト期間を選択します。また、チャート上で実際の取引を視覚的に確認したい場合は、「ビジュアルモード」を有効にしておきます。

バックテストの準備が整ったら、プログラムに対する各種入力パラメータを設定します。初期証拠金、ロットサイズ、そしてZone Recoveryロジックに関連するパラメータを指定します。特に重要な入力としては、「初期ロットサイズ」、「ゾーンサイズ」、「ターゲットサイズ」、「乗数(マルチプライヤー)」などが挙げられます。これらのパラメータを変化させることで、それぞれが戦略の総合的な収益性やリスクにどのような影響を与えるかを分析することができます。以下は、今回変更を加えたパラメータの内容です。

ZoneRecovery zoneRecovery(0.1, 700, 1400, 2.0, _Symbol);
//--- Initialize the ZoneRecovery object with specified parameters.

システムを2024年1月1日から1年間実行するように構成した結果は次のとおりです。

以下は、ストラテジーテスターのグラフです。

グラフ1

以下は、ストラテジーテスターのレポートです。

レポート1

得られたグラフとレポートの結果から、戦略が期待どおりに機能していることが確認できます。しかし、さらにパフォーマンスを向上させるためには、トレーリングロジックを追加して利益最大化に特化することが有効です。これにより、常に完全な利益目標の達成を待つ必要がなくなり、その間にリカバリーモードに入るリスクを軽減しながら、確保できた小さな利益を守り、さらに伸ばすことが可能になります。ただし、トレーリングストップは最初のポジションに対してのみ適用できるため、最初のポジションに対してのみトレーリングをおこない、リカバリーモードに入った場合は、完全なリカバリーがおこなわれるまで待つというロジックを組み込むことができます。したがって、まずはこのためのトレーリングストップ関数を実装する必要があります。

//+------------------------------------------------------------------+
//|      FUNCTION TO APPLY TRAILING STOP                             |
//+------------------------------------------------------------------+
void applyTrailingStop(double slPoints, CTrade &trade_object, int magicNo=0, double minProfitPoints=0){
   double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - slPoints*_Point, _Digits); //--- Calculate the stop loss price for BUY trades
   double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + slPoints*_Point, _Digits); //--- Calculate the stop loss price for SELL trades
   
   for (int i = PositionsTotal() - 1; i >= 0; i--){ //--- Loop through all open positions
      ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position
      if (ticket > 0){ //--- Check if the ticket is valid
         if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number
            if (PositionGetString(POSITION_SYMBOL) == _Symbol && //--- Check if the position belongs to the current symbol
               (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)){ //--- Check if the position matches the given magic number or if no magic number is specified
               
               double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get the opening price of the position
               double positionSl = PositionGetDouble(POSITION_SL); //--- Get the current stop loss of the position
               
               if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ //--- Check if the position is a BUY trade
                  double minProfitPrice = NormalizeDouble(positionOpenPrice + minProfitPoints * _Point, _Digits); //--- Calculate the minimum price at which profit is locked
                  if (buySl > minProfitPrice &&  //--- Check if the calculated stop loss is above the minimum profit price
                      buySl > positionOpenPrice && //--- Check if the calculated stop loss is above the opening price
                      (buySl > positionSl || positionSl == 0)){ //--- Check if the calculated stop loss is greater than the current stop loss or if no stop loss is set
                     trade_object.PositionModify(ticket, buySl, PositionGetDouble(POSITION_TP)); //--- Modify the position to update the stop loss
                  }
               }
               else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ //--- Check if the position is a SELL trade
                  double minProfitPrice = NormalizeDouble(positionOpenPrice - minProfitPoints * _Point, _Digits); //--- Calculate the minimum price at which profit is locked
                  if (sellSl < minProfitPrice &&  //--- Check if the calculated stop loss is below the minimum profit price
                      sellSl < positionOpenPrice && //--- Check if the calculated stop loss is below the opening price
                      (sellSl < positionSl || positionSl == 0)){ //--- Check if the calculated stop loss is less than the current stop loss or if no stop loss is set
                     trade_object.PositionModify(ticket, sellSl, PositionGetDouble(POSITION_TP)); //--- Modify the position to update the stop loss
                  }
               }
            }
         }
      }
   }
}

ここでは、すべてのBUYおよびSELLポジションにトレーリングストップを適用できる関数「applyTrailingStop」を作成します。このトレーリングストップの目的は、市場が取引に有利に動いたときに利益を保護し、確定することです。CTradeオブジェクトを使用して、取引のストップロスレベルを自動的に変更します。トレーリングストップが早期にアクティブ化されないようにするために、ストップロスがトレーリングを開始する前に最低限の利益に達することを要求する条件を含めます。このアプローチにより、早すぎるストップロス調整が防止され、トレーリング前に一定額の利益が確保されます。

この関数では4つの主要なパラメータを定義します。slPointsパラメータは、現在の市場価格から新しいストップロスレベルまでの距離をポイント単位で指定します。trade_objectパラメータはCTradeオブジェクトを参照し、これによりポジションの管理、ストップロスの変更、テイクプロフィットの調整が可能になります。magicNoパラメータは、取引をフィルタリングするための一意の識別子として機能します。magicNoが0に設定されている場合、マジックナンバーに関係なく、すべての取引にトレーリングストップが適用されます。最後に、minProfitPointsパラメータは、トレーリングストップがアクティブになる前に達成する必要がある最小利益(ポイント単位)を定義します。これにより、ポジションが十分な利益を上げた場合にのみストップロスを調整することになります。

ここでは、まず、BUY取引とSELL取引のトレーリングストップロス価格を計算します。BUY取引の場合、現在のBID価格からslPointsを差し引いて新しいストップロス価格を計算します。SELL取引の場合は、現在のASK価格にslPointsを加算して計算します。これらのストップロス価格は、銘柄の価格精度に基づいて正確性を確保するために、_Digitsを使用して正規化されます。この正規化手順により、特定の金融商品の価格が正しい小数点以下の桁数に準拠することが保証されます。

次に、最後のポジションから始めて最初のポジションまで、すべてのポジションをループします。順方向ループ中にポジションを変更すると、ポジションのインデックス付けでエラーが発生する可能性があるため、この逆方向ループのアプローチは不可欠です。各ポジションについて、そのポジションの一意の識別子であるticketを取得します。チケットが有効な場合は、PositionSelectByTicket関数を使用してポジションの詳細を選択し、アクセスします。

ポジションを選択したら、それが現在の銘柄と一致するかどうか、およびそのマジックナンバーが指定されたmagicNoと一致するかどうかを確認します。magicNoが0に設定されている場合、マジックナンバーに関係なく、すべての取引にトレーリングストップが適用されます。一致するポジションを特定した後、それがBUY取引かSELL取引かを判断します。

ポジションがBUY取引の場合、ストップロスが適用されるまでに市場が到達しなければならない最低価格を計算します。この値は、ポジションの始値にminProfitPointsを加算することによって算出されます。次に、計算されたトレーリングストップ価格がポジションの始値と現在のストップロスの両方を超えているかどうかを確認します。これらの条件が満たされた場合、trade_object.PositionModifyを使用してポジションを変更し、BUY取引のストップロス価格を更新します。

ポジションがSELL取引の場合も、同様のプロセスに従います。ポジションの始値からminProfitPointsを差し引いて最小利益価格を計算します。計算されたトレーリングストップ価格が、ポジションの始値と現在のストップロスの両方より低いかどうかを確認します。これらの条件が満たされた場合、trade_object.PositionModifyを使用してポジションを変更し、SELL取引のストップロスを更新します。

この関数が備わったので、まず初期ポジションを見つけるロジックが必要になり、それらの関数にトレーリングストップロジックを追加できます。このためZone Recoveryクラスでブール変数を定義する必要がありますが、重要な点の1つは、それをpublicにしてプログラム内のどこからでもアクセスできるようにすることです。

public:
   bool isFirstPosition;

ここでは、ZoneRecoveryクラス内にpublic変数「isFirstPosition」があります。この変数はブール(bool)型であるため、trueまたはfalseの2つの値のみを保持できます。この関数の目的は、現在の取引がZone Recoveryプロセスの最初のポジションにあるかどうかを追跡することです。isFirstPositionがtrueの場合、以前に取引が開始されておらず、これが初期ポジションであることを示します。この区別は重要です。なぜなら、トレーリングストップロジックを最初の取引に適用すると、最初の取引を処理するロジックが変わるからです。

isFirstPositionはpublicとして宣言されているため、ZoneRecoveryクラスの外部からアクセスして変更できます。これにより、プログラムの他の部分で、ポジションがシリーズの最初のものであるかどうかを確認したり、それに応じてステータスを更新したりできるようになります。ここで、取引を開始する関数内で、ポジションが建てられたら、それが最初のポジションであるかどうかを示すブールフラグを割り当てる必要があります。

if (trade.Buy(currentLotSize, symbol)) {
   lastOrderType = ORDER_TYPE_BUY;         //--- Mark the last trade as BUY.
   lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price.
   CalculateZones();                       //--- Recalculate zones after placing the trade.
   Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
   isFirstPosition = isRecovery ? false : true;
   isRecovery = true;                      //--- Set recovery state to true after the first trade.
   return true;
}

ここで、ポジションがリカバリーポジションとして登録されている場合はisFirstPosition変数をfalseに設定し、isRecovery変数がfalseの場合はtrueに設定します。繰り返しになりますが、コンストラクタとリセット関数では、ターゲット変数のデフォルトをfalseに設定します。そこから、OnTickイベントハンドラに移動し、初期ポジションがあるときにトレーリングストップを適用できます。

if (zoneRecovery.isFirstPosition == true){ //--- Check if this is the first position in the Zone Recovery process
   applyTrailingStop(100, obj_Trade, 0, 100); //--- Apply a trailing stop with 100 points, passing the "obj_Trade" object, a magic number of 0, and a minimum profit of 100 points
}

ここで、変数「zoneRecovery.isFirstPosition」がtrueであるかどうかを確認します。これは、これがZone Recoveryプロセスの最初のポジションであることを示します。そうであれば、applyTrailingStop関数を呼び出します。渡されるパラメータは、トレーリングストップ距離の100ポイント、取引オブジェクトとしてのobj_Trade、取引を識別するマジックナンバー0、および最小利益100ポイントです。これにより、取引の利益が100ポイントに達すると、価格が取引に有利な方向に動くにつれてストップロスを引きずり、利益を保護するためにトレーリングストップが適用されます。ただし、トレーリングストップで取引を終了すると、Zone Recoveryロジックはリセットされないため、その残りがまだ残ります。これにより、既存の取引がない場合でも、システムはリカバリー取引を開始します。以下をご覧ください。

トレーリングストップロジックの不具合GIF

視覚化から、初期ポジションが追跡されたらシステムをリセットする必要があることがわかります。そのために採用する必要があるロジックは次のとおりです。

if (zoneRecovery.isFirstPosition == true && PositionsTotal() == 0){ //--- Check if this is the first position and if there are no open positions
   zoneRecovery.Reset(); //--- Reset the Zone Recovery system, restoring initial settings and clearing previous trade data
}

ここでは、isFirstPosition変数がtrueかどうか、およびポジションが存在していないかどうかを確認します。両方の条件が満たされる場合、初期ポジションがあり、それが何らかの理由でクローズされ、現在は存在しなくなったため、zoneRecovery.Reset()関数を呼び出します。これにより、初期設定が復元され、以前の取引データがすべてクリアされてZone Recoveryシステムがリセットされ、リカバリープロセスが新しく開始されるようになります。これらの変更により、システムは完璧になります。最終テストを実行すると、次の結果が得られます。

以下は、ストラテジーテスターのグラフです。

最終テスターグラフ

以下は、ストラテジーテスターのレポートです。

最終テスターレポート

画像から、リカバリーポジションの数を減らし、ヒット率が大幅に向上していることがわかります。これにより、動的な取引管理ロジックを備えたZone Recoveryシステムを作成するという目的が達成されたことが証明されます。


結論

結論として、本記事では、Zone Recovery戦略を使用して、MetaQuotesLanguage5 (MQL5) EAを構築する方法を解説しました。相対力指数(RSI)インジケーターインジケーターとZone Recoveryロジックを組み合わせることで、取引シグナルの検出、リカバリーポジションの管理、そしてトレーリングストップによる利益確保を実現するシステムを構築しました。シグナルの識別、自動取引の実行、そして動的なポジション回復がこの戦略の主な要素です。

免責条項:この記事は、MQL5プログラムを開発するための教育ガイドとして役立ちます。Zone Recovery RSI戦略は取引管理に構造化されたアプローチを提供しますが、市場の状況は予測不可能なままです。取引には常に金融リスクが伴い、過去のパフォーマンスが将来の結果を保証するものではありません。実際の取引をおこなう前に、適切なテストとリスク管理が不可欠です。

このガイドで紹介したコンセプトを習得することで、より柔軟で適応力のある取引システムを構築し、アルゴリズム取引における新たな戦略を探求できるようになるでしょう。皆さんのコーディングの成功と、取引での成果を心より願っています。

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

添付されたファイル |
古典的な戦略を再構築する(第13回):移動平均線のクロスオーバーにおける遅延の最小化 古典的な戦略を再構築する(第13回):移動平均線のクロスオーバーにおける遅延の最小化
移動平均クロスオーバーは、私たちのコミュニティにおけるトレーダーの間で広く知られている戦略ですが、その基本的な仕組みは誕生以来ほとんど変化していません。本稿では、この戦略に存在する“遅延”を最小限に抑えることを目的とした、わずかながらも重要な改良について紹介します。元の戦略を愛用しているトレーダーの方々にも、今回ご紹介する洞察をもとに、戦略の見直しを検討していただければ幸いです。同一の期間を持つ2つの移動平均を使用することで、戦略の根本的な原則を損なうことなく、遅延を大幅に削減することが可能になります。
プライスアクション分析ツールキットの開発(第6回):Mean Reversion Signal Reaper プライスアクション分析ツールキットの開発(第6回):Mean Reversion Signal Reaper
いくつかの概念は一見するとシンプルに思えるかもしれませんが、実際にそれを形にするのは想像以上に難しいことがあります。この記事では、平均回帰(Mean Reversion)戦略を用いて市場を巧みに分析するエキスパートアドバイザー(EA)の自動化に取り組んだ、革新的なアプローチをご紹介します。この魅力的な自動化プロセスの奥深さを、一緒に紐解いていきましょう。
ログレコードをマスターする(第2回):ログのフォーマット処理 ログレコードをマスターする(第2回):ログのフォーマット処理
この記事では、ライブラリ内でログフォーマッターを作成し、適用する方法について詳しく解説します。フォーマッターの基本構造から実践的な実装例まで幅広く取り上げます。この記事を読み終える頃には、ライブラリ内でログを整形するために必要な知識を習得し、その裏側で何がどのように動作しているのかを理解できるようになります。
スイングエントリーモニタリングEAの開発 スイングエントリーモニタリングEAの開発
年末が近づくと、多くの長期トレーダーは市場の過去を振り返り、その動きや傾向を分析して、将来の動向を予測しようとします。この記事では、MQL5を用いて長期エントリーの監視をおこなうエキスパートアドバイザー(EA)の開発について解説します。手動取引や自動監視システムの不在によって、長期的な取引チャンスを逃してしまうという課題に取り組むことが本稿の目的です。今回は、特に取引量の多い通貨ペアの一つを例に挙げ、効果的な戦略を立案しながらソリューションを構築していきます。