English Deutsch
preview
MQL5での取引戦略の自動化(第23回):トレーリングとバスケットロジックによるゾーンリカバリ

MQL5での取引戦略の自動化(第23回):トレーリングとバスケットロジックによるゾーンリカバリ

MetaTrader 5トレーディング |
220 1
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第22回)では、MetaQuotes Language 5 (MQL5)を用いて、RSI (Relative Strength Index)とEnvelopesを活用したゾーンリカバリー(Zone Recovery)システムを開発し、自動取引と構造化されたリカバリーゾーンによる損失管理をおこないました。第23回では、この戦略をさらに改良し、利益を動的に確保するためのトレーリングストップと、複数の取引シグナルを効率的に処理するマルチバスケットシステムを組み込み、変動の大きい市場環境への適応性を高めます。本記事では以下のトピックを扱います。

  1. 強化されたトレーリングストップとマルチバスケットアーキテクチャの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

これを通じて、テストおよびさらなるカスタマイズに対応可能な高度なMQL5取引システムを完成させます。それでは始めましょう。


強化されたトレーリングストップとマルチバスケットアーキテクチャの理解

私たちが強化しているゾーンリカバリー戦略は、市場が逆行した場合に定義された価格範囲内でカウンタ取引をおこない、潜在的な損失を利益に変えることを目的としています。今回、これを2つの重要な要素で強化します。トレーリングストップとマルチバスケット取引です。トレーリングストップは、市場が有利に動いた際に利益を確定するために不可欠です。これにより、取引を早期にクローズせず、利益を保護できます。これは、特に価格変動の大きいトレンド相場では非常に重要です。マルチバスケット取引も同様に重要で、複数の独立した取引シグナルを同時に管理できるため、より多くのチャンスを捉えつつ、各取引グループごとにリスクを整理して管理できます。以下を参照してください。

トレーリングストップアーキテクチャ

これらの改良は、市場の動きに応じてストップロスレベルを動的に調整するトレーリングストップ機構を統合することで実現します。これにより、取引の成長余地を保ちながら利益を確保できます。マルチバスケット取引では、各取引インスタンスに固有の識別子を付与するシステムを導入し、複数のゾーンリカバリーシステムサイクルを同時に重複なく追跡し、管理できるようにします。これらの機能を既存の相対力指数(RSI)およびエンベロープインジケーターを用いた精度の高い取引エントリーと組み合わせ、トレーリングストップとバスケットシステムが連携して利益保護と取引能力を最適化します。これにより、戦略全体がより堅牢になり、さまざまな市場条件に適応可能となります。これらの改良を実際に実装していきましょう。


MQL5での実装

MQL5でこれらの改良を実装するために、トレーリングストップ機能用の追加ユーザー入力を設定し、複数のリカバリインスタンスを扱うために、最大保有注文数の制限も名称を変更します。

input group "======= EA GENERAL SETTINGS ======="
input TradingLotSizeOptions lotOption = UNFIXED_LOTSIZE;               // Lot Size Option
input double initialLotSize = 0.01;                                    // Initial Lot Size
input double riskPercentage = 1.0;                                     // Risk Percentage (%)
input int    riskPoints = 300;                                         // Risk Points
input int    baseMagicNumber = 123456789;                              // Base Magic Number
input int    maxInitialPositions = 1;                                  // Maximum Initial Positions (Baskets/Signals)
input double zoneTargetPoints = 600;                                   // Zone Target Points
input double zoneSizePoints = 300;                                     // Zone Size Points
input bool   enableInitialTrailing = true;                             // Enable Trailing Stop for Initial Positions
input int    trailingStopPoints = 50;                                  // Trailing Stop Points
input int    minProfitPoints = 50;                                     // Minimum Profit Points to Start Trailing

MQL5での Envelopes Trend取引向けゾーンリカバリーシステムの強化は、まず「EA GENERAL SETTINGS」グループの入力パラメータを更新し、トレーリングストップおよびマルチバスケット取引に対応させることから始めます。入力パラメータには、以下の4つの重要な変更を加えます。magicNumberをbaseMagicNumberに名称変更し、123456789に設定します。これにより、複数のトレードバスケット用に一意のマジックナンバーを生成する出発点となり、マルチバスケットシステムで各バスケットを個別に追跡できます。次に、maxOrdersをmaxInitialPositionsに置き換え、1に設定します。これにより、初期のバスケット数を制限し、複数の取引シグナルを効率的に管理できるようにします。

3番目に、enableInitialTrailingを追加し、ブール値をtrueに設定します。これにより、初期ポジションに対してトレーリングストップを有効または無効にすることができ、新しい利益確保機能を制御可能にします。4番目に、trailingStopPointsを50、minProfitPointsを50に設定します。これにより、トレーリングストップの距離と、トレーリングを有効化するために必要な最小利益を定義し、動的な利益保護を実装します。これらの変更により、システムは複数のバスケットを管理し、利益を効果的に保護できるようになり、さらなる強化の基盤が整います。変更点を明確に示すことで、追跡を容易にし、混乱を避けます。コンパイル後、以下の入力セットが得られます。

新しい入力セット

入力を追加した後、MarketZoneTraderクラスを前方宣言しておくことで、基盤クラスからアクセスできるようにします。これは、複数のトレードインスタンスを扱うために必要です。

//--- Forward Declaration of MarketZoneTrader
class MarketZoneTrader;

ここでは、MarketZoneTraderクラスの前方宣言を導入します。この宣言は、BasketManagerクラス定義の前に追加します。BasketManagerクラスは、このクラスの直後に定義する予定で、MarketZoneTraderを参照できるようにするためです。完全なクラス定義がまだなくても参照できるようにするため、この前方宣言が必要です。この変更は、マルチバスケットシステムの新機能に必須です。BasketManagerが管理するマルチバスケットシステムにおいては、異なるバスケット取引用に複数のMarketZoneTraderインスタンスを作成し、管理する必要があります。先にMarketZoneTraderを宣言することで、新しいクラス内で使用した際にコンパイラが認識でき、複数の同時取引サイクルを効率的にサポートできるようになります。その後、マネージャークラスを定義することができます。

//--- Basket Manager Class to Handle Multiple Traders
class BasketManager {
private:
   MarketZoneTrader* m_traders[];                                        //--- Array of trader instances
   int               m_handleRsi;                                        //--- RSI indicator handle
   int               m_handleEnvUpper;                                   //--- Upper Envelopes handle
   int               m_handleEnvLower;                                   //--- Lower Envelopes handle
   double            m_rsiBuffer[];                                     //--- RSI data buffer
   double            m_envUpperBandBuffer[];                            //--- Upper Envelopes buffer
   double            m_envLowerBandBuffer[];                            //--- Lower Envelopes buffer
   string            m_symbol;                                          //--- Trading symbol
   int               m_baseMagicNumber;                                 //--- Base magic number
   int               m_maxInitialPositions;                             //--- Maximum baskets (signals)

   //--- Initialize Indicators
   bool initializeIndicators() {
      m_handleRsi = iRSI(m_symbol, PERIOD_CURRENT, 8, PRICE_CLOSE);
      if (m_handleRsi == INVALID_HANDLE) {
         Print("Failed to initialize RSI indicator");
         return false;
      }
      m_handleEnvUpper = iEnvelopes(m_symbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1);
      if (m_handleEnvUpper == INVALID_HANDLE) {
         Print("Failed to initialize upper Envelopes indicator");
         return false;
      }
      m_handleEnvLower = iEnvelopes(m_symbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4);
      if (m_handleEnvLower == INVALID_HANDLE) {
         Print("Failed to initialize lower Envelopes indicator");
         return false;
      }
      ArraySetAsSeries(m_rsiBuffer, true);
      ArraySetAsSeries(m_envUpperBandBuffer, true);
      ArraySetAsSeries(m_envLowerBandBuffer, true);
      return true;
   }

}

バスケット取引の管理を容易にするために、MarketZoneTraderクラスの複数のインスタンスとインジケーターデータを管理するためのprivateメンバーを持つBasketManagerクラスを定義します。まず、個々のバスケット取引を保持するために、MarketZoneTraderポインタの配列「m_traders」を作成します。各バスケットは独立したゾーンリカバリーサイクルを表します。この変更は非常に重要であり、前バージョンの単一インスタンス方式とは異なり、複数の取引シグナルを同時に管理できるようになります。また、RSIおよびエンベロープのインジケーターハンドルを保持するために、m_handleRsi、m_handleEnvUpper、m_handleEnvLowerを宣言します。RSIおよびエンベロープのデータを格納するために、m_rsiBuffer、m_envUpperBandBuffer、m_envLowerBandBuffer配列を用意し、インジケーターの管理をMarketZoneTraderからBasketManagerに移すことで、バスケット全体で一元的に制御できるようにします。

さらに、取引銘柄を保持するm_symbol、各バスケットごとに一意のマジックナンバーを生成するためのm_baseMagicNumber、アクティブなバスケット数を制限するm_maxInitialPositionsを追加します。これらは新しい入力パラメータ「maxInitialPositions」に対応しています。nitializeIndicators関数では、RSIインジケーターをiRSI関数で期間8に設定し、EnvelopesインジケーターをiEnvelopes関数で上限バンドを期間150・偏差0.1、下限バンドを期間95・偏差1.4で設定します。いずれもINVALID_HANDLEをチェックし、失敗時にはPrint関数でログ出力をおこないます。また、m_rsiBuffer、m_envUpperBandBuffer、m_envLowerBandBuffer配列をArraySetAsSeries関数を使用して時系列配列として設定します。この新しいクラス構造により、複数のトレードバスケットを効率的に連携し、管理でき、全バスケットでインジケーターデータを集中管理することで、一貫したシグナル生成が可能になります。続いて、各バスケットのポジションをカウントしやすくするためのロジックを追加し、不要なバスケットをクリーンアップする処理を実装します。

//--- Count Active Baskets
int countActiveBaskets() {
   int count = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() != MarketZoneTrader::INACTIVE) {
         count++;
      }
   }
   return count;
}

//--- Cleanup Terminated Baskets
void cleanupTerminatedBaskets() {
   int newSize = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL && m_traders[i].getCurrentState() == MarketZoneTrader::INACTIVE) {
         delete m_traders[i];
         m_traders[i] = NULL;
      }
      if (m_traders[i] != NULL) newSize++;
   }
   MarketZoneTrader* temp[];
   ArrayResize(temp, newSize);
   int index = 0;
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL) {
         temp[index] = m_traders[i];
         index++;
      }
   }
   ArrayFree(m_traders);
   ArrayResize(m_traders, newSize);
   for (int i = 0; i < newSize; i++) {
      m_traders[i] = temp[i];
   }
   ArrayFree(temp);
}

ここでは、BasketManagerクラスに新たにcountActiveBaskets関数とcleanupTerminatedBaskets関数の2つを追加します。まず、アクティブなバスケット取引の数を追跡するためにcountActiveBaskets関数を作成します。count変数を0に初期化し、ArraySize関数を使用してm_traders配列をループ処理します。各m_tradersの要素がNULLでない場合、その要素の状態をgetCurrentStateを介して取得し、その値がMarketZoneTrader::INACTIVEでないかを確認します。アクティブな場合は、countをインクリメントします。最後にcountを返すことで、現在稼働中のバスケット数を監視できるようにします。これは、新しいバスケットを開く際に、m_maxInitialPositionsの制限内に収まっていることを保証するために重要です。

次に、非アクティブなバスケットを削除してメモリを最適化するためのcleanupTerminatedBaskets関数を作成します。まず、m_traders配列をループしてNULLでないエントリをカウントします。各トレーダーがNULLでなく、かつgetCurrentStateの戻り値がMarketZoneTrader::INACTIVEの場合、deleteを使用してメモリを解放し、そのエントリをNULLに設定します。残っているNULLでないトレーダーの数をnewSizeで追跡します。次に、一時配列tempを作成し、ArrayResize関数でnewSizeにリサイズします。m_tradersからtempにNULLでないトレーダーをindexカウンタを使用してコピーします。その後、m_tradersをArrayFreeでクリアし、newSizeにリサイズして、tempからトレーダーを戻します。最後に、ArrayFreeを使用してtempを解放します。このクリーンアップ処理により、終了したバスケットが確実に削除され、システムは効率的な状態を維持し、新しい取引にすぐ対応できるようになります。続いて、publicアクセス修飾子のセクションに移り、クラスメンバーや要素を初期化・破棄する際のコンストラクタおよびデストラクタの扱いを変更します。

public:
   BasketManager(string symbol, int baseMagic, int maxInitPos) {
      m_symbol = symbol;
      m_baseMagicNumber = baseMagic;
      m_maxInitialPositions = maxInitPos;
      ArrayResize(m_traders, 0);
      m_handleRsi = INVALID_HANDLE;
      m_handleEnvUpper = INVALID_HANDLE;
      m_handleEnvLower = INVALID_HANDLE;
   }

   ~BasketManager() {
      for (int i = 0; i < ArraySize(m_traders); i++) {
         if (m_traders[i] != NULL) delete m_traders[i];
      }
      ArrayFree(m_traders);
      cleanupIndicators();
   }

まず、BasketManagerのコンストラクタを定義します。このコンストラクタはsymbol、baseMagic、maxInitPosの3つのパラメータを受け取ります。これらをそれぞれm_symbol、m_baseMagicNumber、m_maxInitialPositionsに代入し、取引シンボル、一意のバスケット識別用ベースマジックナンバー、そしてアクティブなバスケットの最大数を設定します。 ArrayResize関数を使用してm_traders配列をサイズ0に初期化し、インジケーターセットアップの準備として、m_handleRsi、m_handleEnvUpper、m_handleEnvLowerをINVALID_HANDLEに設定します。このコンストラクタは、マルチバスケットシステムを構成するために非常に重要な要素です。

次に、リソースをクリーンアップするためのデストラクタ「~BasketManagerを」作成します。一般的に、デストラクタはチルダ(~)を接頭辞として持つことを、ここで改めて確認しておきます。ArraySize関数を使用してm_traders配列をループし、NULLでないMarketZoneTraderインスタンスをdeleteで削除してメモリを解放します。続いて、ArrayFreeでm_traders配列をクリアし、cleanupIndicatorsを呼び出してインジケーターハンドルおよびバッファを解放します。これにより、EA停止時にシステムがクリーンに終了し、メモリリークを防止することができます。前バージョンでは、メモリリークが発生していることが判明した後に、削除ロジックをOnDeinitイベントハンドラ内に直接追加する必要がありましたが、今回は最初からメモリリーク対策が必要であると分かっているため、早い段階で追加しています。続いて、初期化ロジックを変更し、既存のポジションをそれぞれのバスケットに読み込めるようにする必要があります。これを実現するために、以下のロジックを実装しています。

bool initialize() {
   if (!initializeIndicators()) return false;
   //--- Load existing positions into baskets
   int totalPositions = PositionsTotal();
   for (int i = 0; i < totalPositions; i++) {
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket)) {
         if (PositionGetString(POSITION_SYMBOL) == m_symbol) {
            long magic = PositionGetInteger(POSITION_MAGIC);
            if (magic >= m_baseMagicNumber && magic < m_baseMagicNumber + m_maxInitialPositions) {
               //--- Check if basket already exists for this magic
               bool exists = false;
               for (int j = 0; j < ArraySize(m_traders); j++) {
                  if (m_traders[j] != NULL && m_traders[j].getMagicNumber() == magic) {
                     exists = true;
                     break;
                  }
               }
               if (!exists && countActiveBaskets() < m_maxInitialPositions) {
                  createNewBasket(magic, ticket);
               }
            }
         }
      }
   }
   Print("BasketManager initialized with ", ArraySize(m_traders), " existing baskets");
   return true;
}

/*
//--- PREVIOUS INITIALIZATION
int initialize() {
   //--- Initialization Start
   m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number
   int totalPositions = PositionsTotal();                           //--- Get total positions
   
   for (int i = 0; i < totalPositions; i++) {                       //--- Iterate positions
      ulong ticket = PositionGetTicket(i);                          //--- Get ticket
      if (PositionSelectByTicket(ticket)) {                         //--- Select position
         if (PositionGetString(POSITION_SYMBOL) == m_tradeConfig.marketSymbol && PositionGetInteger(POSITION_MAGIC) == m_tradeConfig.tradeIdentifier) { //--- Check symbol and magic
            if (activateTrade(ticket)) {                            //--- Activate position
               Print("Existing position activated: Ticket=", ticket); //--- Log activation
            } else {
               Print("Failed to activate existing position: Ticket=", ticket); //--- Log failure
            }
         }
      }
   }
   
   m_handleRsi = iRSI(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 8, PRICE_CLOSE); //--- Initialize RSI
   if (m_handleRsi == INVALID_HANDLE) {                             //--- Check RSI
      Print("Failed to initialize RSI indicator");                  //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvUpper = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1); //--- Initialize upper Envelopes
   if (m_handleEnvUpper == INVALID_HANDLE) {                        //--- Check upper Envelopes
      Print("Failed to initialize upper Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvLower = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4); //--- Initialize lower Envelopes
   if (m_handleEnvLower == INVALID_HANDLE) {                        //--- Check lower Envelopes
      Print("Failed to initialize lower Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   ArraySetAsSeries(m_rsiBuffer, true);                             //--- Set RSI buffer
   ArraySetAsSeries(m_envUpperBandBuffer, true);                    //--- Set upper Envelopes buffer
   ArraySetAsSeries(m_envLowerBandBuffer, true);                    //--- Set lower Envelopes buffer
   
   Print("EA initialized successfully");                            //--- Log success
   return INIT_SUCCEEDED;                                           //--- Return success
   //--- Initialization End
}
*/

ここでは、BasketManagerクラス内に更新版のinitialize関数を実装し、インジケーターを初期化し、既存のポジションをそれぞれのバスケットに読み込むことで、マルチバスケット取引機能をサポートします。まず、initializeIndicatorsを呼び出してRSIおよびエンベロープインジケーターをセットアップします。失敗した場合はfalseを返して、システムが必要なマーケットデータを確実に取得できるようにします。従来のバージョンでは、インジケーターのセットアップをMarketZoneTraderのinitialize関数内で個別に処理していましたが、今回はBasketManagerに集約することで、複数のバスケット間でインジケータデータを共有できるようにしました。次に、PositionsTotal関数を使用して既存のポジションを確認し、各ポジションをループ処理します。各ポジションのチケットをPositionGetTicket関数で取得します。

PositionSelectByTicketが成功し、そのポジションの銘柄がPositionGetStringを介して取得した値とm_symbolに一致する場合、そのマジックナンバーをPositionGetIntegerで取得します。このマジックナンバーがm_baseMagicNumberからm_baseMagicNumber + m_maxInitialPositionsの範囲内にあるかを確認します。次に、同じマジックナンバーを持つバスケットが既に存在するかどうかを確認するために、m_traders配列をループし、NULLでないエントリに対してgetMagicNumberを呼び出します。もし該当するバスケットが存在せず、かつcountActiveBasketsの結果がm_maxInitialPositionsを下回っている場合、createNewBasketをマジックナンバーとチケットを引数に呼び出し、そのポジションを新しいバスケットにロードします。最後に、Print関数を使用してm_tradersのArraySizeを出力し、初期化されたバスケット数をログに記録します。その後、trueを返して初期化完了を示します。プログラムを実行すると、次の結果が得られます。

バスケットの初期化

ここからはティック処理に進みます。processTick関数内では、各ティックごとに既存のバスケットを処理し、新しいシグナルが確定した際に新しいバスケットを作成する必要があります。これは、確定シグナルに基づいて取引を開始するだけでよかった従来バージョンとは異なります。

void processTick() {
   //--- Process existing baskets
   for (int i = 0; i < ArraySize(m_traders); i++) {
      if (m_traders[i] != NULL) {
         m_traders[i].processTick(m_rsiBuffer, m_envUpperBandBuffer, m_envLowerBandBuffer);
      }
   }
   cleanupTerminatedBaskets();

   //--- Check for new signals on new bar
   if (!isNewBar()) return;

   if (!CopyBuffer(m_handleRsi, 0, 0, 3, m_rsiBuffer)) {
      Print("Error loading RSI data. Reverting.");
      return;
   }
   if (!CopyBuffer(m_handleEnvUpper, 0, 0, 3, m_envUpperBandBuffer)) {
      Print("Error loading upper envelopes data. Reverting.");
      return;
   }
   if (!CopyBuffer(m_handleEnvLower, 1, 0, 3, m_envLowerBandBuffer)) {
      Print("Error loading lower envelopes data. Reverting.");
      return;
   }

   const int rsiOverbought = 70;
   const int rsiOversold = 30;
   int ticket = -1;
   ENUM_ORDER_TYPE signalType = (ENUM_ORDER_TYPE)-1;

   double askPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_ASK), Digits());
   double bidPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_BID), Digits());

   if (m_rsiBuffer[1] < rsiOversold && m_rsiBuffer[2] > rsiOversold && m_rsiBuffer[0] < rsiOversold) {
      if (askPrice > m_envUpperBandBuffer[0]) {
         if (countActiveBaskets() < m_maxInitialPositions) {
            signalType = ORDER_TYPE_BUY;
         }
      }
   } else if (m_rsiBuffer[1] > rsiOverbought && m_rsiBuffer[2] < rsiOverbought && m_rsiBuffer[0] > rsiOverbought) {
      if (bidPrice < m_envLowerBandBuffer[0]) {
         if (countActiveBaskets() < m_maxInitialPositions) {
            signalType = ORDER_TYPE_SELL;
         }
      }
   }

   if (signalType != (ENUM_ORDER_TYPE)-1) {
      //--- Create new basket with unique magic number
      int newMagic = m_baseMagicNumber + ArraySize(m_traders);
      if (newMagic < m_baseMagicNumber + m_maxInitialPositions) {
         MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, newMagic);
         ticket = newTrader.openInitialOrder(signalType); //--- Open INITIAL position
         if (ticket > 0 && newTrader.activateTrade(ticket)) {
            int size = ArraySize(m_traders);
            ArrayResize(m_traders, size + 1);
            m_traders[size] = newTrader;
            Print("New basket created: Magic=", newMagic, ", Ticket=", ticket, ", Type=", EnumToString(signalType));
         } else {
            delete newTrader;
            Print("Failed to create new basket: Ticket=", ticket);
         }
      } else {
         Print("Maximum initial positions (baskets) reached: ", m_maxInitialPositions);
      }
   }
}

この関数内では、まずArraySize関数を使用してm_traders配列をループし、各NULLでないMarketZoneTraderインスタンスに対してprocessTick関数を呼び出します。この際、m_rsiBuffer、m_envUpperBandBuffer、およびm_envLowerBandBufferを引数として渡し、各バスケットのロジックを個別に処理します。これは、単一の取引サイクルを直接管理していた従来バージョンのprocessTickとは異なります。続いて、cleanupTerminatedBasketsを呼び出して非アクティブなバスケットを削除し、リソースを効率的に使用できるようにします。次に、isNewBarを使用して新しいバーが確定したときのみ、新規シグナルを確認します。結果がfalseの場合はリソース節約のために処理を終了します。

その後、CopyBufferを使用してm_handleRsi、m_handleEnvUpper、およびm_handleEnvLowerのインジケータデータをそれぞれのバッファに読み込みます。データの読み込みに失敗した場合は、Printでエラーログを出力し、処理を終了します。従来バージョンではこの処理をMarketZoneTrader内でおこなっていましたが、今回はBasketManager側で集中管理するように変更されています。次に、rsiOverboughtを70、rsiOversoldを30に設定し、ticketおよびsignalTypeを初期化します。SymbolInfoDouble関数を用いてSYMBOL_ASKおよびSYMBOL_BIDからaskPriceとbidPriceを取得し、NormalizeDoublee関数で正規化します。

買いシグナルの場合、m_rsiBufferが売られ過ぎを示し、かつaskPriceがm_envUpperBandBufferを上回っているとき、countActiveBasketsがm_maxInitialPositionsを下回っていればsignalTypeをORDER_TYPE_BUYに設定します。売りシグナルの場合、m_rsiBufferが買われ過ぎを示し、かつbidPriceがm_envLowerBandBufferを下回っているとき、signalTypeをORDER_TYPE_SELLに設定します。有効なsignalTypeが存在する場合、m_baseMagicNumberにArraySize(m_traders)を加算して一意のマジックナンバーを生成します。この値がm_maxInitialPositionsの範囲内であれば、新しいマジックナンバーと入力パラメータを用いて新しいMarketZoneTraderをインスタンス化します。

その後、openInitialOrderをsignalTypeを引数に呼び出し、戻り値のticketが有効であり、かつactivateTradeが成功した場合、ArrayResizeでm_traders配列を拡張して新しいトレーダーを追加します。成功時にはPrintとEnumToStringを使用してログに出力します。一方、失敗した場合はそのトレーダーをdeleteで削除して失敗をログに記録し、バスケット制限に達している場合にはその旨を通知します。新しい取引が始まった後、それらに対応する新しいバスケットを作成する必要があります。そのためのロジックを以下に実装します。

private:
   void createNewBasket(long magic, ulong ticket) {
      MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, magic);
      if (newTrader.activateTrade(ticket)) {
         int size = ArraySize(m_traders);
         ArrayResize(m_traders, size + 1);
         m_traders[size] = newTrader;
         Print("Existing position loaded into basket: Magic=", magic, ", Ticket=", ticket);
      } else {
         delete newTrader;
         Print("Failed to load existing position into basket: Ticket=", ticket);
      }
   }

BasketManagerクラスのprivateセクション内に、新たにcreateNewBasket関数を実装します。この関数は、既存ポジションに対して新しいバスケットを作成、管理するために追加されたもので、マルチバスケット取引機能をサポートする重要な要素です。まず、入力パラメータlotOption、initialLotSize、riskPercentage、riskPoints、zoneTargetPoints、zoneSizePoints、および指定されたmagicナンバーを用いて、新しいMarketZoneTraderインスタンスnewTraderを作成します。これにより、一意のバスケットが構成されます。従来バージョンでは、ユーザー入力を初期化段階で設定しており、ゾーンが1つのインスタンスのみだったため、すべての新規ポジションに同じ設定が適用されていました。しかし今回の実装では、各バスケットを新しいクラスインスタンスとして整理することで、個別の管理と拡張性を実現しています。以下のコードは、比較を容易にするためにその実装部分を示したものです。

//--- PREVIOUS VERSION OF NEW CLASS INSTANCE
//--- Global Instance
MarketZoneTrader *trader = NULL;                                        //--- Declare trader instance

int OnInit() {
   //--- EA Initialization Start
   trader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints, zoneSizePoints); //--- Create trader instance
   return trader.initialize();                                           //--- Initialize EA
   //--- EA Initialization End
}

次に、newTraderに対してactivateTradeを呼び出し、与えられたticketを用いて既存のポジションをバスケットにロードします。成功した場合、ArraySizeでm_traders配列の現在サイズを取得し、ArrayResizeで1つ拡張して、newTraderを新しいスロットに追加します。成功時には、Printでmagicとticketの値を含めてログに出力します。activateTradeが失敗した場合は、newTraderをdeleteで削除してメモリを解放し、Printで失敗をログに記録します。この関数により、既存のポジションをそれぞれのバスケットに整理できるようになり、従来バージョンの単一インスタンス方式とは異なり、マルチバスケットシステムの主要機能が実現されます。このクラスにより、バスケットを効率的に管理できるようになります。続いて、基盤クラスを改良し、新しいマルチバスケット機能およびトレーリングストップ機能に対応させます。まずは、そのメンバーから見ていきましょう。

//--- Modified MarketZoneTrader Class
class MarketZoneTrader {
private:
   enum TradeState { INACTIVE, RUNNING, TERMINATING };

   struct TradeMetrics {
      bool   operationSuccess;
      double totalVolume;
      double netProfitLoss;
   };

   struct ZoneBoundaries {
      double zoneHigh;
      double zoneLow;
      double zoneTargetHigh;
      double zoneTargetLow;
   };

   struct TradeConfig {
      string         marketSymbol;
      double         openPrice;
      double         initialVolume;
      long           tradeIdentifier;
      string         initialTradeLabel;  //--- Label for initial positions
      string         recoveryTradeLabel; //--- Label for recovery positions
      ulong          activeTickets[];
      ENUM_ORDER_TYPE direction;
      double         zoneProfitSpan;
      double         zoneRecoverySpan;
      double         accumulatedBuyVolume;
      double         accumulatedSellVolume;
      TradeState     currentState;
      bool           hasRecoveryTrades;  //--- Flag to track recovery trades
      double         trailingStopLevel;  //--- Virtual trailing stop level
   };

   struct LossTracker {
      double tradeLossTracker;
   };

   TradeConfig           m_tradeConfig;
   ZoneBoundaries        m_zoneBounds;
   LossTracker           m_lossTracker;
   string                m_lastError;
   int                   m_errorStatus;
   CTrade                m_tradeExecutor;
   TradingLotSizeOptions m_lotOption;
   double                m_initialLotSize;
   double                m_riskPercentage;
   int                   m_riskPoints;
   double                m_zoneTargetPoints;
   double                m_zoneSizePoints;
}

ここでは、プログラムを強化するために、MarketZoneTraderクラスのprivateセクションを修正し、トレーリングストップおよび改善された取引ラベル機能をサポートする新しい機能を追加します。コア構造は維持しつつ、TradeConfig構造体に重要な変更を加え、強化された戦略に対応させます。TradeStateenumeration(INACTIVE、RUNNING、TERMINATING)はそのまま保持し、TradeMetrics、ZoneBoundaries、LossTracker構造体も前バージョンと同様に維持します。これらは、取引状態、パフォーマンス指標、ゾーン境界、損失追跡を引き続き管理します。

TradeConfig構造体では、initialTradeLabelおよびrecoveryTradeLabelの2つの新しい文字列変数を追加します。これらのラベルにより、初期取引とリカバリ取引を個別にタグ付けでき、各バスケット取引内での識別と追跡が容易になります。特に、新システムで複数バスケット取引を管理する場合に有用です。さらに、ブール値のhasRecoveryTradesを追加し、バスケット取引にリカバリー取引が含まれるかどうかを追跡します。これはトレーリングストップを適切に有効化または無効化するために重要です。加えて、各バスケットの仮想トレーリングストップレベルを格納するtrailingStopLevel(double型)を追加し、初期取引に対して動的な利益保護を可能にします。

メンバー変数では、m_tradeConfig、m_zoneBounds、m_lossTracker、m_lastError、m_errorStatus、m_tradeExecutor、m_lotOption、m_initialLotSize、m_riskPercentage、m_riskPoints、m_zoneTargetPoints、m_zoneSizePointsを保持しますが、それぞれの役割は新しいトレーリングストップおよびマルチバスケット機能をサポートするように更新されます。特に、m_handleRsiやm_rsiBufferなどのインジケータ関連変数は削除され、現在はBasketManagerクラスで集中管理されるため、各トレーダーは個々のバスケット取引の運用に専念できるようになります。コンストラクタおよびデストラクタでは、新機能を扱えるようにいくつかの変数をわずかに変更する必要があります。

public:
   MarketZoneTrader(TradingLotSizeOptions lotOpt, double initLot, double riskPct, int riskPts, double targetPts, double sizePts, long magic) {
      m_tradeConfig.currentState = INACTIVE;
      ArrayResize(m_tradeConfig.activeTickets, 0);
      m_tradeConfig.zoneProfitSpan = targetPts * _Point;
      m_tradeConfig.zoneRecoverySpan = sizePts * _Point;
      m_lossTracker.tradeLossTracker = 0.0;
      m_lotOption = lotOpt;
      m_initialLotSize = initLot;
      m_riskPercentage = riskPct;
      m_riskPoints = riskPts;
      m_zoneTargetPoints = targetPts;
      m_zoneSizePoints = sizePts;
      m_tradeConfig.marketSymbol = _Symbol;
      m_tradeConfig.tradeIdentifier = magic;
      m_tradeConfig.initialTradeLabel = "EA_INITIAL_" + IntegerToString(magic); //--- Label for initial positions
      m_tradeConfig.recoveryTradeLabel = "EA_RECOVERY_" + IntegerToString(magic); //--- Label for recovery positions
      m_tradeConfig.hasRecoveryTrades = false; //--- Initialize recovery flag
      m_tradeConfig.trailingStopLevel = 0.0; //--- Initialize trailing stop
      m_tradeExecutor.SetExpertMagicNumber(magic);
   }

   ~MarketZoneTrader() {
      ArrayFree(m_tradeConfig.activeTickets);
   }

まず、MarketZoneTraderのconstructorから始めます。今回は追加のmagicパラメータを受け取り、各バスケット取引に一意のマジックナンバーを割り当てられるようになっています。従来バージョンでは固定のマジックナンバーを使用していました。改善された取引ラベル機能をサポートするため、m_tradeConfig.initialTradeLabelをEA_INITIALにmagicを付加(IntegerToStringを使用)し、m_tradeConfig.recoveryTradeLabelをEA_RECOVERYにmagicを付加して設定します。これにより、各バスケット取引内で初期取引とリカバリー取引を個別に識別できるようになります。さらに、リカバリー取引の状態を追跡するためにm_tradeConfig.hasRecoveryTradesをfalseに初期化し、仮想トレーリングストップ用のm_tradeConfig.trailingStopLevelを0.0に設定します。これらはいずれも新しい機能です。最後に、m_tradeExecutorをSetExpertMagicNumberでmagicを用いて設定します。主要な変更点はハイライトしており、確認が容易です。

次に、~MarketZoneTraderデストラクタを従来バージョンのcleanupと比較して簡略化します。現在はm_tradeConfig.activeTicketsをArrayFreeでクリアするだけです。インジケータのクリーンアップはBasketManagerで集中管理されるため、デストラクタの役割はバスケット取引固有のリソースに焦点を当てる形に縮小されます。続いて、初期取引に対してトレーリングストップレベルとリカバリー状態を初期化できるよう、取引をアクティブ化する関数を更新します。

bool activateTrade(ulong ticket) {

   m_tradeConfig.hasRecoveryTrades = false;
   m_tradeConfig.trailingStopLevel = 0.0;
   
   //--- THE REST OF THE LOGIC REMAINS
   
   return true;
}

ここでは、最初の取引に対してトレーリングストップレベルを0に、リカバリー状態をfalseに初期化するロジックを追加します。これは、このポジションがバスケット取引内で最初のポジションであることを示すためです。最後に、初期ポジションを開く関数を追加できます。

int openInitialOrder(ENUM_ORDER_TYPE orderType) {
   //--- Open INITIAL position based on signal
   int ticket;
   double openPrice;
   if (orderType == ORDER_TYPE_BUY) {
      openPrice = NormalizeDouble(getMarketAsk(), Digits());
   } else if (orderType == ORDER_TYPE_SELL) {
      openPrice = NormalizeDouble(getMarketBid(), Digits());
   } else {
      Print("Invalid order type [Magic=", m_tradeConfig.tradeIdentifier, "]");
      return -1;
   }
   double lotSize = 0;
   if (m_lotOption == FIXED_LOTSIZE) {
      lotSize = m_initialLotSize;
   } else if (m_lotOption == UNFIXED_LOTSIZE) {
      lotSize = calculateLotSize(m_riskPercentage, m_riskPoints);
   }
   if (lotSize <= 0) {
      Print("Invalid lot size [Magic=", m_tradeConfig.tradeIdentifier, "]: ", lotSize);
      return -1;
   }
   if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, 0, 0, m_tradeConfig.initialTradeLabel)) {
      ticket = (int)m_tradeExecutor.ResultOrder();
      Print("INITIAL trade opened [Magic=", m_tradeConfig.tradeIdentifier, "]: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize);
   } else {
      ticket = -1;
      Print("Failed to open INITIAL order [Magic=", m_tradeConfig.tradeIdentifier, "]: Type=", EnumToString(orderType), ", Volume=", lotSize);
   }
   return ticket;
}

ここでは、MarketZoneTraderクラスのパブリックセクションに新たにopenInitialOrder関数を実装します。この関数は、特定のバスケット取引に対して初期ポジションをオープンし、明確に識別できるようにすることで、マルチバスケットおよび改善された取引ラベル機能をサポートします。まず、ticketおよびopenPriceを初期化します。orderTypeがORDER_TYPE_BUYの場合、getMarketAskを使用してopenPriceを取得しNormalizeDoubleとDigitsで正規化します。ORDER_TYPE_SELLの場合はgetMarketBidを使用します。orderTypeが無効な場合は、Printでm_tradeConfig.tradeIdentifierを含むエラーログを出力し、-1を返します。

次に、m_lotOptionに基づいてlotSizeを決定します。FIXED_LOTSIZEの場合はm_initialLotSizeを使用し、UNFIXED_LOTSIZEの場合は、calculateLotSizeを呼び出し、m_riskPercentageおよびm_riskPointsを用いて計算します。lotSizeが無効な場合は、Printでエラーを記録し、-1を返します。その後、m_tradeExecutor.PositionOpenを使用してポジションをオープンします。引数にはm_tradeConfig.marketSymbol、orderType、lotSize、openPrice、および初期取引を明確に識別するためのm_tradeConfig.initialTradeLabelを渡します。成功した場合、ResultOrderでticketを設定し、Printでm_tradeConfig.tradeIdentifierおよびEnumToStringを含めてログに記録します。失敗した場合は、ticketを「-1」に設定し、エラーをログに出力します。最後に、ticketを返します。従来バージョンのopenOrder関数とは異なり、この関数は新しいinitialTradeLabelを使用し、初期ポジションにのみ焦点を当てています。これにより、マルチバスケットシステムに沿った動作が可能になります。コンパイルすると、次の結果が得られます。

初期バスケット

画像から、初期取引をオープンし、それに対応する新しいバスケット取引インスタンスを作成できることが確認できます。次に、ポジションのトレーリングストップ機能を管理できるように、トレーリングロジックを実装する必要があります。

void evaluateMarketTick() {
   if (m_tradeConfig.currentState == INACTIVE) return;
   if (m_tradeConfig.currentState == TERMINATING) {
      finalizePosition();
      return;
   }
   double currentPrice;
   double profitPoints = 0.0;

   //--- Handle BUY initial position
   if (m_tradeConfig.direction == ORDER_TYPE_BUY) {
      currentPrice = getMarketBid();
      profitPoints = (currentPrice - m_tradeConfig.openPrice) / _Point;

      //--- Trailing Stop Logic for Initial Position
      if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
         //--- Calculate desired trailing stop level
         double newTrailingStop = currentPrice - trailingStopPoints * _Point;
         //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
         if (profitPoints >= minProfitPoints + trailingStopPoints) {
            if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop > m_tradeConfig.trailingStopLevel) {
               m_tradeConfig.trailingStopLevel = newTrailingStop;
               Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
            }
         }
         //--- Check if price has hit trailing stop
         if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice <= m_tradeConfig.trailingStopLevel) {
            Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " <= TrailingStop=", m_tradeConfig.trailingStopLevel);
            finalizePosition();
            return;
         }
      }

      //--- Zone Recovery Logic
      if (currentPrice > m_zoneBounds.zoneTargetHigh) {
         Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh);
         finalizePosition();
         return;
      } else if (currentPrice < m_zoneBounds.zoneLow) {
         Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow);
         triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice);
      }
   }
   //--- Handle SELL initial position
   else if (m_tradeConfig.direction == ORDER_TYPE_SELL) {
      currentPrice = getMarketAsk();
      profitPoints = (m_tradeConfig.openPrice - currentPrice) / _Point;

      //--- Trailing Stop Logic for Initial Position
      if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) {
         //--- Calculate desired trailing stop level
         double newTrailingStop = currentPrice + trailingStopPoints * _Point;
         //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints
         if (profitPoints >= minProfitPoints + trailingStopPoints) {
            if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop < m_tradeConfig.trailingStopLevel) {
               m_tradeConfig.trailingStopLevel = newTrailingStop;
               Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points");
            }
         }
         //--- Check if price has hit trailing stop
         if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice >= m_tradeConfig.trailingStopLevel) {
            Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " >= TrailingStop=", m_tradeConfig.trailingStopLevel);
            finalizePosition();
            return;
         }
      }

      //--- Zone Recovery Logic
      if (currentPrice < m_zoneBounds.zoneTargetLow) {
         Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow);
         finalizePosition();
         return;
      } else if (currentPrice > m_zoneBounds.zoneHigh) {
         Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh);
         triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice);
      }
   }
}

ここでは、プログラムを強化し、evaluateMarketTick関数を更新して、既存のゾーンリカバリーのロジックを維持しつつトレーリングストップロジックを組み込みます。まず、m_tradeConfig.currentStateがINACTIVEまたはTERMINATINGかを確認し、従来どおり処理を終了するか、finalizePositionを呼び出します。買いポジションの場合(m_tradeConfig.directionがORDER_TYPE_BUY)、getMarketBidでcurrentPriceを取得し、profitPointsを「(currentPrice - m_tradeConfig.openPrice) / _Point」で計算します。新しいトレーリングストップロジックでは、enableInitialTrailingがtrue、m_tradeConfig.hasRecoveryTradesがfalse、かつprofitPointsがminProfitPoints以上である場合に、newTrailingStopを「currentPrice - trailingStopPoints * _Point」で計算します。さらに、profitPointsが「minProfitPoints + trailingStopPoints」を超え、かつm_tradeConfig.trailingStopLevelが0.0またはnewTrailingStopより低い場合、m_tradeConfig.trailingStopLevelを更新し、Printでログに出力します。

m_tradeConfig.trailingStopLevelが設定され、currentPriceがそれを下回った場合、トリガーをログに記録し、finalizePositionを呼び出してポジションをクローズします。ゾーンリカバリーロジックは従来どおり維持され、currentPriceがm_zoneBounds.zoneTargetHighを超えた場合にポジションをクローズし、m_zoneBounds.zoneLowを下回った場合にはtriggerRecoveryTradeで売りリカバリー取引を発動します。

売りポジションの場合(m_tradeConfig.directionがORDER_TYPE_SELL)、getMarketAskでcurrentPriceを取得し、profitPointsを逆に計算します。トレーリングストップロジックは買いの場合と同様で、newTrailingStopを「currentPrice + trailingStopPoints *_Point」で設定し、条件を満たせばm_tradeConfig.trailingStopLevelを更新し、currentPriceがそれを超えた場合にポジションをクローズします。ゾーンリカバリーロジックは、currentPriceがm_zoneBounds.zoneTargetLowを下回った場合にポジションをクローズし、m_zoneBounds.zoneHighを上回った場合には買いリカバリートレードを発動します。物理的なトレーリングストップは使用せず、システム全体を完全に制御できるようにしています。これにより、すべてのインスタンスを監視、管理することが可能です。以下は、トレーリングストップ機能を実行した後の出力例です。

トレーリングストップインスタンス

画像から、ポジションをトレーリングし、価格がトレーリングレベルまで戻ったときにクローズできることが確認できます。最後に、BasketManagerのインスタンスを作成し、それをグローバルに管理に使用します。

//--- Global Instance
BasketManager *manager = NULL;

int OnInit() {
   manager = new BasketManager(_Symbol, baseMagicNumber, maxInitialPositions);
   if (!manager.initialize()) {
      delete manager;
      manager = NULL;
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason) {
   if (manager != NULL) {
      delete manager;
      manager = NULL;
      Print("EA deinitialized");
   }
}

void OnTick() {
   if (manager != NULL) {
      manager.processTick();
   }
}

グローバルインスタンスとイベントハンドラを更新し、以前バージョンで使用していたMarketZoneTraderクラスの代わりに新しいBasketManagerクラスを使用します。これにより、複数のバスケット取引を集中管理することで、マルチバスケット取引機能をサポートします。まず、グローバルにBasketManagerクラスのポインタmanagerを宣言し、NULLで初期化します。従来はMarketZoneTraderのtraderポインタを使用していました。この変更は重要で、単一インスタンス方式だった以前のバージョンとは異なり、1つのマネージャーで複数のバスケット取引を管理できるようになります。

OnInitイベントハンドラでは、managerの新しいBasketManagerインスタンスを作成し、引数として_Symbol、baseMagicNumber、maxInitialPositionsを渡して、現在のチャート、バスケット識別用のユニークなマジックナンバー、および最大バスケット数を設定します。次にmanager.initializeを呼び出してインジケーターをセットアップし、既存ポジションをロードします。失敗した場合はmanagerを削除してNULLに設定し、INIT_FAILEDを返します。成功した場合はINIT_SUCCEEDEDを返します。

OnDeinitイベントハンドラでは、managerがNULLでない場合、deleteで削除し、NULLに設定して、Printで初期化解除をログに出力します。OnTickでは、managerがNULLでないことを確認した上で、manager.processTickを呼び出し、すべてのバスケット取引のマーケットティックを処理します。これにより、従来のtrader.processTick呼び出しを置き換え、複数のバスケット取引のティック処理を集中管理できるようになります。コンパイルすると、次の結果が得られます。

最終取引

画像から、提供されたマジックナンバーから構築された異なるラベルを使用して、個別のシグナルバスケットを作成して管理できることがわかります。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

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

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

まとめとして、今回の改良により、MQL5におけるEnvelopes Trend取引向けのゾーンリカバリーシステムに、トレーリングストップおよびマルチバスケット取引機能を導入しました。第22回の基礎を踏まえ、BasketManagerクラスや更新されたMarketZoneTrader関数などの新しいコンポーネントを追加することで、より柔軟で堅牢な取引フレームワークを構築しています。これらの改良により、trailingStopPointsやmaxInitialPositionsなどのパラメータを調整することで、さらにカスタマイズ可能なシステムとなります。

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

これらの改良を活用することで、本システムをさらに洗練させたり、そのアーキテクチャを応用して新しい戦略を作成することができ、アルゴリズム取引のスキル向上に役立ちます。取引をお楽しみください。

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

最後のコメント | ディスカッションに移動 (1)
Tyrone Chan
Tyrone Chan | 31 8月 2025 において 07:20

初値売り取引が成立しません。

取引ロジックと関係がありますか?

MQL5における特異スペクトル解析 MQL5における特異スペクトル解析
本記事は、特異スペクトル解析(SSA: Singular Spectrum Analysis)の概念に不慣れな方を対象に、MQL5で利用可能な組み込みツールを実際に活用できるようになるためのガイドとして作成されたものです。
知っておくべきMQL5ウィザードのテクニック(第74回): 教師あり学習で一目均衡表とADX Wilderのパターンを利用する 知っておくべきMQL5ウィザードのテクニック(第74回): 教師あり学習で一目均衡表とADX Wilderのパターンを利用する
前回の記事では、一目均衡表とADXのインジケーターペアを紹介しました。今回は、このペアを教師あり学習でどのように改善できるかを見ていきます。一目均衡表とADXは、サポート/レジスタンスとトレンドを補完する組み合わせとして機能します。今回の教師あり学習アプローチでは、ディープスペクトル混合カーネルを用いたニューラルネットワークを活用し、このインジケーターペアの予測精度を微調整します。通常どおり、この処理はMQL5ウィザードでエキスパートアドバイザー(EA)を組み立てる際に利用できるカスタムシグナルクラスファイル内でおこないます。
MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2) - 加重投票方策 MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2) - 加重投票方策
本記事では、アンサンブル内で最適な戦略数を決定することがどれほど複雑な課題であるか、その解決がMetaTrader 5の遺伝的アルゴリズム最適化ツールを用いることで容易になるかを検討します。さらに、バックテストおよび最適化の高速化を目的として、MQL5クラウドも主要なリソースとして活用します。これらの議論を通じて、初期のアンサンブル結果に基づき、取引戦略を評価し、改善するための統計モデルを開発するための基盤を整えることを目的としています。
グラフ理論:ダイクストラ法を取引に適用する グラフ理論:ダイクストラ法を取引に適用する
ダイクストラ法は、グラフ理論における古典的な最短経路探索手法であり、市場ネットワークをモデル化することで取引戦略の最適化に応用できます。トレーダーは、ローソク足チャート上の価格データをグラフとして扱い、最も効率的な「経路」を見つけるためにダイクストラ法を使用できます。