English Deutsch
preview
MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2)

MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2)

MetaTrader 5 |
15 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

本連載では、複数の取引戦略を一つの統合的なアンサンブル戦略に組み合わせる創造的な手法を取り上げています。個別の戦略にはそれぞれ限界がありますが、それらを組み合わせることで、より強力で実用的な戦略を構築することを目的としています。

前回は、すべての取引戦略の基盤となるスーパークラスを構築しました。このスーパークラスを利用することで、MQL5上において最初の戦略である移動平均クロスオーバー戦略を実装することができました。その後、この柔軟な戦略クラスとハードコーディング版の同一戦略を比較し、両者のパフォーマンスが一致することを確認しました。

MetaTrader 5のストラテジーテスターを使うことで、有効なパラメータ設定を発見できました。しかし、最適なパラメータを自力で見つけるのは容易ではありません。そのため、MetaTrader 5に搭載されている遺伝的アルゴリズムによる最適化機能は非常に有効です。プロセスを自動化することで、時間と労力を大幅に削減できます。 

さらにフォワードテストを用いて、安定したパラメータセットを選び出しましたこれにより、実装が正確かつ効率的であることを確認できました。

今回のテーマは、さらに一歩踏み込み、相対力指数(RSI: Relative Strength Index)を基にした2つ目の戦略を構築し、前回の移動平均クロスオーバー戦略と統合することです。こうして、より堅牢で収益性の高いアンサンブル戦略の構築を目指します。この新しい戦略についても、MetaTrader 5のストラテジーテスターを活用して最適化をおこないます。ただし、その前に「パラメータの最小化」という重要な概念について触れておく必要があります。

戦略に新しい要素を加えると、パラメータの数は急速に増加します。パラメータが多すぎる戦略は最適化が難しくなり、場合によってはほとんど不可能になることもあります。したがって、できる限りパラメータの数を抑えることが重要です。特定のパラメータを固定値とすることで、影響度の大きいパラメータに絞って最適化に集中できます。

具体的な例として、元の移動平均戦略はフォワードテストにおいてシャープレシオ1.29を記録し、101件の取引で133.51ドルの利益を上げました。これに対し、今回構築するRSIベースの戦略は、シャープレシオ2.68、52件の取引で214.08ドルの利益を達成しています。

つまり、新しい戦略は取引回数を減らすことで、市場リスクを抑えつつ、より大きな利益を実現しています。これはまさに、取引戦略設計において理想的な成果といえるでしょう。

ストラテジーテスターを適切に活用することで、さらに収益性の高い設定を見つけ出すことが可能です。ただし留意すべきは、MetaTrader 5のストラテジーテスターが万能の解決策ではないという点です。根本的に欠陥のある戦略を利益を生む戦略に変えることはできません。

戦略そのものに問題があれば、どれほど最適化を重ねても改善は望めないのです。とはいえ、健全な戦略であれば、最適化は結果を保証するものではなくても、成功の可能性を大きく高める手段となります。堅固な基盤と適切なツールを備えていれば、戦略のパフォーマンスを引き出す可能性には限界がありません。


MQL5の始め方

今回のディスカッションは、まずRSIミッドポイント戦略用のクラスを構築するところから始めます。最初の目標は、戦略クラスが必要とする依存関係を正しく読み込むことです。最初の依存関係は、単一バッファインジケーター用に作成したRSIクラスです。加えて、すべての戦略で使用するスーパークラス(親クラス)も必要になります。

//+------------------------------------------------------------------+
//|                                                  RSIMidPoint.mqh |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/ja/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/ja/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <VolatilityDoctor\Strategies\Parent\Strategy.mqh>

依存関係を読み込んだので、次にRSIミッドポイント戦略クラスとそのメンバーの定義を始めます。このミッドポイント戦略は、すべての戦略の基底クラス(スーパークラス)を継承していて、コロン(:)の構文を使って表します。その後、ミッドポイント戦略クラスのメンバーを定義します。定義すべきメンバーはそれほど多くありません。また重要な点として、親クラスで作成したすべての仮想(virtual)メソッドは、RSIミッドポイントクラスでも再びvirtualとして明示的に記載する必要があります。

class RSIMidPoint : public Strategy
  {
private:
                     //--- The instance of the RSI used in this strategy
                     RSI *my_rsi;

public:
                     //--- Class constructor 
                     RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price);
                     
                     //--- Class destructor
                    ~RSIMidPoint();
                    
                    //--- Class overrides
                    virtual bool Update(void);
                    virtual bool BuySignal(void);
                    virtual bool SellSignal(void);
  };
 

これで、各メソッドをRSI戦略向けに具体的にどのように実装するかを検討する段階に進めます。まずUpdateメソッドについてです。このメソッドでは、RSIインジケーターの値を更新し、次にRSIインジケーターから取得した現在の値がゼロでないことを確認します。もしゼロでなければ、更新が正常におこなわれたことを意味します。逆にゼロであれば、何らかの問題が発生したことを意味します。

//+------------------------------------------------------------------+
//| Our strategy update method                                       |
//+------------------------------------------------------------------+
bool RSIMidPoint::Update(void)
   {
      //--- Set the indicator value
      my_rsi.SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      
      //--- Check readings are valid
      if(my_rsi.GetCurrentReading() != 0) return(true);
      
      //--- Something went wrong
      return(false);
   }  

次に、買いおよび売りのシグナルの場合を検討します。買いシグナルは、RSIの値が50未満の場合に生成されます。一方、売りシグナルは、RSIの値が50を上回る場合に生成されます。

//+------------------------------------------------------------------+
//| Check for our buy signal                                         |
//+------------------------------------------------------------------+
bool RSIMidPoint::BuySignal(void)
   {
      //--- Buy signals when the RSI is below 50
      return(my_rsi.GetCurrentReading()<50);
   }

//+------------------------------------------------------------------+
//| Check for our sell signal                                        |
//+------------------------------------------------------------------+
bool RSIMidPoint::SellSignal(void)
   {
      //--- Sell signals when the RSI is above 50
      return(my_rsi.GetCurrentReading()>50);
   }

最後に、クラスのコンストラクタとデストラクタについて考えます。クラスのコンストラクタは、RSIインジケーターに必要な詳細情報を受け取ります。具体的には、ユーザーはRSIを適用する銘柄、時間足、期間、そして価格を指定する必要があります。これらの情報がすべて指定されたら、それらを読み込み、新しいRSIインジケーターのインスタンスを作成します。

ただし、注意点として、このRSIインジケーターのインスタンスは、MetaTrader 5に標準で付属するRSIインジケーターとは異なります。これは私たちが定義したカスタム型であり、多くの便利な関数を持っていて、今後使用する予定です。それでも基本的な機能は同じであり、必要に応じてこれらのメソッドのいくつかを自分で一から実装しても構いません。

//+------------------------------------------------------------------+
//| Our class constructor                                            |
//+------------------------------------------------------------------+
RSIMidPoint::RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price)
  {
   my_rsi = new RSI(user_symbol,user_timeframe,user_period,user_price);
   Print("RSI-Mid-Point Strategy Loaded.");
  }

最後に、クラスのデストラクタでは、作成したカスタムRSIオブジェクトへのポインタを削除します。

//+------------------------------------------------------------------+
//| Our class destructor                                             |
//+------------------------------------------------------------------+
RSIMidPoint::~RSIMidPoint()
  {
   delete my_rsi;
  }
//+------------------------------------------------------------------+

RSIミッドポイント戦略をカプセル化するクラスを定義したので、次にこのクラスがエラーなく構築されているかどうかを確認する必要があります。そのためには、まず戦略のハードコーディング版を用いて基準となるパフォーマンスレベルを確立し、クラスが同じパフォーマンスを再現できるかをテストします。 

この2つの戦略が同等であれば、同じ期間でテストした場合に、同じ収益性レベルを示し、同じ統計値を生成するはずです。基準を定義するにあたって、まず最初におこなうべきことは、必要なシステム定数を列挙することです。たとえば、RSIを適用する価格、RSIの期間、時間足などです。これらの定数は両方のテストで固定する必要があり、公平な比較をおこなうためには必須となります。

//+------------------------------------------------------------------+
//|                                          MSA Test 2 Baseline.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/ja/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/ja/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define RSI_PRICE        PRICE_CLOSE
#define RSI_PERIOD       15
#define RSI_TIME_FRAME   PERIOD_D1
#define HOLDING_PERIOD   5

その後、必要な依存関係を読み込みます。今回の場合、必要なライブラリはおおよそ3つだけです。

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Time\Time.mqh>

加えて、いくつかのグローバル変数も定義する必要があります。ここで注目すべき点は、グローバル変数をカスタム型とシステム型の2種類に分けられることです。カスタム型はユーザーが定義した型であり、システム型はMetaTrader 5のすべてのインストール環境で使用可能な型です。たとえばdoubleやfloatなどがシステム型にあたります。このようにグローバル変数を分類しておくことで、コードの可読性や保守性を向上させることができます。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade    Trade;
TradeInfo *TradeInformation;
Time      *TradeTime;

//--- System Types
double rsi[],ma_close[];
int    rsi_handler;
int    position_timer;

次に、エキスパートアドバイザー(EA)の初期化プロセスを構築します。EAが初めて初期化される際には、まずRSIハンドラーを自分で作成します。その後、必要なカスタム型の動的インスタンスも作成します。これらのカスタム型は、特に時間を管理したり、最小ロットサイズなどの重要な取引情報を取得するために使用します。その後、RSIハンドラが安全に読み込まれたかどうかを確認します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Our technical indicator
   rsi_handler = iRSI(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);

//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),RSI_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME);


//--- Safety checks
   if(rsi_handler == INVALID_HANDLE)
      return(false);
   
//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

EAが使用されなくなった場合、使用していないインジケーターを解放するとともに、作成した動的オブジェクトも削除します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the indicators and dynamic objects
   IndicatorRelease(rsi_handler);

   delete TradeTime;
   delete TradeInformation;

  }
//--- End of Deinit Scope

OnTick関数では、まず作成したライブラリを使って新しい日足のローソク足が形成されたかどうかを確認します。新しいローソク足が形成されている場合、テクニカル指標を更新した後、現在ポジションが保有されているかどうかを確認します。ポジションが保有されていない場合は、ポジション時間がリセットされていることを確認し、取引シグナルの有無をチェックします。一方、すでに取引が保有されている場合は、取引が満期に達して決済すべきかどうかを確認します。それ以外の場合は、そのままポジションを保有し続けます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {

      //--- Update our technical indicators
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

また、先に説明したいくつかのメソッドの実装も考慮する必要があります。たとえば、Updateメソッドは単純にCopyBufferを呼び出すだけで、RSIハンドラから取得したインジケーターの値を、あらかじめ指定した配列にコピーします。ここでコピーするのは、RSIインジケーターの現在の値のみです。過去の値をコピーする必要はありません。

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Call the CopyBuffer method to get updated indicator values
   CopyBuffer(rsi_handler,0,0,1,rsi);
  }
//--- End of Update Scope

次に、CheckSignalメソッドについてです。CheckSignalメソッドは、先に定義した取引シグナルを確認する役割を持ちます。具体的には、RSIの値が50未満であれば買いシグナルを出し、逆にRSIの値が50を上回っていれば売りシグナルを出します。

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Buy signals when the RSI is below 50
   if(rsi[0] < 50)
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Sell signals when the RSI is above 50
   else
      if(rsi[0] > 50)
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }

  }
//--- End of CheckSignal Scope

最後に、冒頭で定義したシステム定数を統合します。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef RSI_PRICE        
#undef RSI_PERIOD       
#undef RSI_TIME_FRAME   
#undef HOLDING_PERIOD   
//+------------------------------------------------------------------+

バックテストを開始するにあたり、まず学習期間を選択します。今回のテストは、2022年1月1日から2025年5月1日までを対象とします。なお、今回のテストは日足でおこないます。

図1:基準測定のために選択したバックテスト期間の日数

シミュレーションをより実践的におこなうために、[延滞]を[ランダム遅延]に設定します。これにより、実際の市場で発生するレイテンシやスリッページ、その他実取引での緊張要因を再現できます。

図2:バックテストに使用したテスト設定

下の図3に示す資産曲線は、RSI取引戦略クラスで再現すべき目標です。

図3:戦略のハードコーディング版で生成された資産曲線

基準戦略のパフォーマンスに関する詳細な統計情報も確認できます。同じ期間でテストした際に、クラスが同じ統計値を出すことを確認することで、クラスの正当性を証明したいと考えています。


図4:戦略パフォーマンスの詳細統計 

次に、RSI戦略クラスが正しく動作するかを確認するため、同様のテストを実施する必要があります。そのため、以前に定義したシステム定数やグローバル変数などの多くの要素はほぼ変更せずに使用します。今回アプリケーションに加える主な変更点は、EAの初期化フェーズで戦略クラスを初期化することです。 

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Strategies\RSIMidPoint.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade               Trade;
TradeInfo            *TradeInformation;
Time                 *TradeTime;
RSIMidPoint          *RSIMid;

//--- System Types
int    position_timer;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),RSI_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME);
   RSIMid           = new RSIMidPoint(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope
加えて、OnDeinit関数を更新し、新たに作成したオブジェクトが正しく削除されるようにする必要があります。
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete RSIMid;
  }
//--- End of Deinit Scope
最後に、CheckSignalメソッドが適切に呼び出されるように確認する必要があります。
//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when RSI is below 50
   if(RSIMid.BuySignal())
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short positions when the RSI is above 50
   else
      if(RSIMid.SellSignal())
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }
  }
//--- End of CheckSignal Scope

次に、先ほどの基準テストで使用したのと同じ期間を対象に、構築したRSI戦略クラスのテストを実施します。

図5:RSI取引戦略クラス版のテスト日付を選択

両方のテストは、図2で指定した[ランダム遅延]条件で実施され、バックテスト設定下で類似した資産曲線が得られました。

図6:戦略クラス版の資産曲線はハードコーディング版の基準曲線と一致する

さらに、詳細分析においても両方のテストは同じ統計値を示しました。これは、クラスがエラーなく正しく実装されたことを示す良い指標です。したがって、次は移動平均クロスオーバー戦略とRSI戦略のクラスの統合に進みます。

図7:戦略クラス版のパフォーマンスを分析した詳細統計は基準値と一致する 

クラス版とハードコーディング版の戦略が同一の結果を出すことを確認できたので、次に移動平均クロスオーバー戦略とRSI戦略を統合し、新しいアンサンブル戦略を作成します。まず、固定するパラメータをいくつか選択する必要があります。パラメータ空間が過剰に大きくなると、意味のある最適化がほぼ不可能になるため、パラメータ空間の成長を制御することが重要です。 

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/ja/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/ja/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
//--- Fix any parameters that can afford to remain fixed
#define MA_SHIFT         0
#define MA_TYPE          MODE_EMA
#define RSI_PRICE        PRICE_CLOSE

さらに、2つの戦略を同時に実行するにあたり、これらの戦略がMQL5内でどのように相互作用するかを定義する必要があります。この管理には列挙型を使用して、いくつかの関連する状態を表現することができます。

そこで、STRATEGY_MODEという列挙型を作成しました。この列挙型には3つの設定が可能です。最初の設定(値0)は投票ポリシーです。このポリシーでは、両方の戦略が同じ方向にシグナルを出した場合にのみ取引をおこないます。言い換えれば、ロングポジションを開くには、移動平均戦略とRSI戦略の両方がロングのシグナルを出す必要があります。

次の代替モードでは、ロングポジションの責任をRSI戦略に割り当て、ショートポジションの責任を移動平均戦略に割り当てます。最後のモードはこの割り当てを逆にして、ロング取引の責任を移動平均戦略に、ショート取引の責任をRSI戦略に割り当てます。

//+------------------------------------------------------------------+
//| User defined enumerator                                          |
//+------------------------------------------------------------------+

enum STRATEGY_MODE
  {
   MODE_ONE   = 0, //Voting Policy
   MODE_TWO   = 1, //RSI Buy & MA Sell
   MODE_THREE = 2  //MA Sell & RSI Buy
  };
このように戦略を構築することで、入力パラメータの総数を管理可能な5つに減らすことができました。その後、前述した依存関係を読み込み、すでに馴染みのある変数を初期化します。 

//+------------------------------------------------------------------+
//| User Inputs                                                      |
//+------------------------------------------------------------------+
input   group          "Moving Average Strategy Parameters"
input   int             MA_PERIOD                       =        10;//Moving Average Period


input   group          "RSI Strategy Parameters"
input   int             RSI_PERIOD                      =         15;//RSI Period

input   group          "Global Strategy Parameters"
input   ENUM_TIMEFRAMES STRATEGY_TIME_FRAME             = PERIOD_D1;//Strategy Timeframe
input   int             HOLDING_PERIOD                  =         5;//Position Maturity Period
input   STRATEGY_MODE   USER_MODE                       =         0;//Operation Mode For Our Strategy

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>
#include <VolatilityDoctor\Strategies\RSIMidPoint.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade               Trade;
Time                 *TradeTime;
TradeInfo            *TradeInformation;
RSIMidPoint          *RSIMid;
OpenCloseMACrossover *MACross;

//--- System Types
int                  position_timer;

初期化関数では、開発過程で使用した同じクラスを読み込みます。EAが終了する際には、作成されたすべての動的オブジェクトが正しく削除されることを確認します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),STRATEGY_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),STRATEGY_TIME_FRAME);
   MACross          = new OpenCloseMACrossover(Symbol(),STRATEGY_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);
   RSIMid           = new RSIMidPoint(Symbol(),STRATEGY_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete MACross;
   delete RSIMid;
  }
//--- End of Deinit Scope

OnTick関数では、以前に説明したのと同じロジックを適用します。このロジックについてはすでに詳細に解説したため、本セクションではOnTick関数の説明は省略します。Update関数は若干修正されており、それぞれの戦略に対して別々のUpdateメソッドを呼び出すようになっています。最後に、CheckSignalメソッドが最も大きな変更を受けています。これにより、取引を開始する前にまず現在の戦略モードを判定する必要があります。

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Update the strategy
   RSIMid.Update();
   MACross.Update();
  }
//--- End of Update Scope

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {

//--- Both Stratetgies Should Cast The Same Vote
   if(USER_MODE == 0)
     {
      //--- Long positions when the close moving average is above the open
      if(MACross.BuySignal() && RSIMid.BuySignal())
        {
         Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
         return;
        }

      //--- Otherwise short
      else
         if(MACross.SellSignal()  && RSIMid.SellSignal())
           {
            Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
            return;
           }
     }

//--- RSI Opens All Long Positions & The Moving Average Opens Short Positions
   else
      if(USER_MODE == 1)
        {

         if(RSIMid.BuySignal())
           {
            Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
            return;
           }

         //--- Otherwise short
         else
            if(MACross.SellSignal())
              {
               Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
               return;
              }
        }

      //--- RSI Opens All Short Positions & The Moving Average Opens Long Positions
      else
         if(USER_MODE == 2)
           {

            if(MACross.BuySignal())
              {
               Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
               return;
              }

            //--- Otherwise short
            else
               if(RSIMid.SellSignal())
                 {
                  Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
                  return;
                 }
           }

  }
//--- End of CheckSignal Scope

最後に、すべてのロジックが実行された後、プログラムを正常に終了させるために、すべてのシステム定数を未定義化します。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_SHIFT
#undef RSI_PRICE
#undef MA_TYPE
//+------------------------------------------------------------------+

それでは、アプリケーションで使用する最適なパラメータ設定を探す作業を開始します。まず、先ほど作成したアプリケーションを選択し、[フォワードテスト]フィールドを1/2に設定します。これにより、遺伝的最適化は、過去データの前半部分を学習用に使用し、後半部分はオプティマイザーから隠され、オプティマイザーが有効だと判断した設定を検証するために使用されます。 

図8:フォワードテストの設定

前回の2つのテストと同様に[ランダム遅延]を設定した後、今回はオプティマイザーを選択する必要があります。もし計算資源に余裕がある場合は[完全アルゴリズム(遅い)]など他のオプティマイザーも試すことができます。

図9:[オプティマイズ]フィールドで「遺伝的アルゴリズム(速い)」を選択する

画面下部の[パラメータ]タブを選択すると、テスト対象の戦略パラメータを制御するパネルが表示されます。それぞれのパラメータにチェックを入れ、検索範囲の開始値と終了値を設定します。ここで、ステップサイズも重要です。ステップサイズを1にすると非常に詳細な結果が得られますが、必要なステップ数が膨大になる可能性があります。一般的には、2、4、5、あるいは非常に大きな検索の場合は10程度の間隔が推奨されます。

図10:パラメータを検索する範囲の設定

テストを開始すると、フォワードテストの結果が散布図として表示されます。散布図の各点は、ユニークな戦略パラメータセットで実行された1回のテストを表しています。

図11:ストラテジーテスターの結果は縦軸に利益を取った2次元散布図として可視化可能

遺伝的アルゴリズムで選択された設定でも、学習サンプル外で戦略は利益を維持しました。これは、実際の資金でアルゴリズムを運用した場合の将来の安定性の可能性を示す良い指標です。

図12:戦略による資産曲線はアウトオブサンプルでも上昇傾向を示す

新しいシステムは、以前のバージョンよりもはるかに収益性が高いことが確認できます。「「投票ポリシー」の組み合わせが最も安定していることも明確にわかります。 

図13:戦略を追加したことで、アプリケーションのパフォーマンスが向上した

これらは、前回のディスカッションで得られたフォワードテストの最良結果です。新しいシステムは、以前の1戦略しか認識していなかったバージョンを上回るパフォーマンスを示しています。

図14:アプリケーションが1つの戦略のみの場合の最良結果



結論

本記事では、MQL5におけるオブジェクト指向プログラミング(OOP)の原則を人間の創造性と組み合わせて活用することの多くの利点を解説しました。複数の戦略を一つの統合されたシステムに組み込む方法は一つではなく、さまざまなアプローチが存在することを示しました。オブジェクト指向プログラミングは、このような創造的な取り組みにおいて強力かつ柔軟なツールであり、構造的で制御可能、かつ予測可能な開発を実現します。

ここで紹介した基礎的な概念を踏まえることで、読者はご自身の取引戦略をさらに洗練させ、最適化するための力を身につけることができます。MetaTrader 5に搭載されているストラテジーテスターを活用すれば、実際に検証をおこないながら実装を改善することが可能です。

また本記事では、戦略における入力パラメータの数を最小限に抑えることの重要性についても強調しました。パラメータが多すぎる戦略は最適化が非常に難しくなります。計算負荷の一部をMQL5クラウドネットワークサービスにオフロードすることは可能ですが、設計のシンプルさを維持することが、より良い成果や堅牢な戦略につながることが多いのです。

本記事で紹介した原則を応用することで、読者は独自のアンサンブル取引システムを構築するための実践的な知見を得られたはずです。次回は戦略に第三のレイヤーを導入する予定であり、これは将来の統計モデルを構築するための確固たる基盤となるでしょう。

ファイル名 ファイルの説明 
MSA Test 2 Baseline.mq5 RSI取引戦略のハードコーディング版テストファイル 
MSA Test 2 Class.mq5 RSI戦略クラスのテストファイル
MSA Test 2.ex5 アンサンブル取引戦略のコンパイル済みファイル 
MSA Test 2.mq5 開発した移動平均とRSIを組み合わせたアプリケーションのソースコード
RSIMidPoint.mqh RSIミッドポイント戦略のMQL5クラス 

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

添付されたファイル |
MSA_Test_2.ex5 (47.2 KB)
MSA_Test_2.mq5 (7.64 KB)
RSIMidPoint.mqh (3.51 KB)
MQL5での取引戦略の自動化(第20回):CCIとAOを使用した多銘柄戦略 MQL5での取引戦略の自動化(第20回):CCIとAOを使用した多銘柄戦略
この記事では、CCI (Commodity Channel Index)とAO (Awesome Oscillator)を用いてトレンド反転を検出する多銘柄取引戦略を作成します。戦略の設計、MQL5での実装、バックテストのプロセスについて解説します。記事の最後には、パフォーマンス改善のためのヒントも紹介します。
MQL5からDiscordへのメッセージの送信、Discord-MetaTrader 5ボットの作成 MQL5からDiscordへのメッセージの送信、Discord-MetaTrader 5ボットの作成
Telegramと同様に、Discordもその通信APIを使用してJSON形式の情報やメッセージを受信することができます。本記事では、MetaTrader5からDiscordの取引コミュニティに取引シグナルやアップデートを送信するためにDiscord APIをどのように利用できるかを探っていきます。
ログレコードをマスターする(第9回):ビルダーパターンの実装とデフォルト設定の追加 ログレコードをマスターする(第9回):ビルダーパターンの実装とデフォルト設定の追加
本記事では、Logifyライブラリの利用をビルダーパターンと自動的なデフォルト設定によって大幅に簡単にする方法をご紹介します。ここでは、専用ビルダーの構造、スマートな補完機能を活用した利用方法、手動で設定をおこなわなくても動作するログ確保方法について解説します。さらに、MetaTrader 5ビルド5100向けの調整についても触れます。
データサイエンスとML(第44回):ベクトル自己回帰(VAR)を用いた外国為替OHLC時系列予測 データサイエンスとML(第44回):ベクトル自己回帰(VAR)を用いた外国為替OHLC時系列予測
本記事では、ベクトル自己回帰(VAR: Vector Autoregression)モデルを用いて、複数の通貨ペアのOHLC(始値、高値、安値、終値)時系列データを予測する方法を解説します。VARモデルの実装、学習、MetaTrader5上でのリアルタイム予測までをカバーし、通貨間の相互依存関係を分析して取引戦略の改善に役立てることができます。