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

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

MetaTrader 5 |
10 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

アルゴリズム取引に取り組むうえで、私たちが直面する課題は少なくありません。本連載でもすでにいくつかの課題を取り上げてきました。たとえば、統計モデルは将来の価格水準を予測するよりも、将来のテクニカル指標の値を予測する方が容易であることがわかっています。

また、取引戦略とそれを適用する市場との関係をモデル化する取引システムの有効性についても検討してきました。

そして、私たちのモデルは一貫して、古典的な「直接価格を予測する」という課題を置き換え、代わりにこうした代替的な課題に取り組むことで、より良いパフォーマンスを発揮してきました。直接的な価格予測は困難ですが、問題の枠組みを変えることによって、古典的な課題に固執するモデルを上回る成果を、同じ統計的手法を使いながらも実現できるのです。

本日は、これらの知見を基盤にした新たな戦略の可能性を探ります。もしアプリケーションが3つの異なる取引戦略を理解し、それらを常に同時に利用するのではなく、その時点で最も収益性の高い戦略を選択して定期的に切り替えることができるとしたらどうでしょうか。アプリケーションが戦略を切り替えられるのであれば、3つのうちから最良の戦略を選び取ることはできるでしょうか。

このような仕組みが実現すれば、単に固定的にすべての戦略を同時運用したり、無造作に組み合わせたりするアルゴリズムよりも、はるかに有用な取引アプリケーションとなる可能性があります。

こうしたアプローチの有効性を測定するためには、まず私たちの統計モデルが上回るべき基準となるベースラインのパフォーマンスを定義する必要があります。

本記事では、移動平均クロスオーバー継続戦略、相対力指数(RSI: Relative Strength Index)モメンタム戦略、ウィリアムズ%R (Williams Percent Range)トレンドブレイクアウト戦略の3つの独立した取引戦略を組み合わせます。それぞれの戦略については後ほど詳細に説明します。

さらに、本記事ではMetaTrader 5ターミナルに備わる強力なツールの1つである「ウォークフォワードテスト」を紹介します。ウォークフォワードテストはバックテストとは異なる手法であり、その違いについても説明します。

特に、新しい戦略パラメータを生成して検証するオプティマイザと組み合わせることで、戦略にとって堅牢かつ収益性の高い設定を見出すことが可能になります。MetaTrader 5には、高速および低速の遺伝的アルゴリズムを備えた高度なオプティマイザが標準で搭載されています。

このように、本連載で扱ってきた信頼性の高いオブジェクト指向設計の原則と、強力な戦略テストツールを組み合わせることで、私たちの統計モデルと競合しうる強力な取引アルゴリズムを設計、テスト、検証していきます。


MQL5の始め方

本記事では、複数の戦略をどのように最適に組み合わせて、実用的に機能する1つの戦略へ統合できるかという課題を取り上げます。複数の戦略を同時に用いる場合、ハードコードされた解法はほとんど存在しません。

戦略を組み合わせる試みは創造性を必要とするため刺激的ですが、一方で予期しない副作用を最小限に抑える必要があります。

トレーダーはしばしば複数の戦略を組み合わせて使用します。たとえば、ある戦略がポジションをオープンし、別の戦略がクローズのタイミングを決定するといった具合です。各戦略は問題の一部分に特化しており、この人間的なアプローチを模倣したいと考えています。その過程で、MetaTrader 5のストラテジーテスターを活用して適切な戦略パラメータを見つける方法も示します。

戦略を確実に組み合わせるために、各戦略をクラスとしてカプセル化します。各クラスは正しく機能することを証明するためにテストをおこなう必要があります。すべての戦略バリエーションの基底となる単一の親クラスを「親」と呼びます。このクラスには、パラメータの更新や売買シグナルの確認といった共通関数を含めます。

この親クラスを継承する各クラスは、売買シグナルの定義を独自に再実装します。これを実現するために、共有メソッドをvirtual(仮想関数)として定義し、各戦略が安全にオーバーライドできるようにします。

ここまでで、最初のクラスである「親戦略クラス」を構築する準備が整いました。

MQL5では、各クラスはclassキーワードとクラス名から始まります。ファイル名をクラス名と同一にするのが標準的な慣習です。

また、サブクラスでオーバーライド可能にする関数にはvirtual指定子を付け、コンパイラに対して明示します。これにより、各戦略は自身に適した方法でこれらのメソッドを安全に定義できます。

//+------------------------------------------------------------------+
//|                                                     Strategy.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"

class Strategy
  {
private:
                     int  buffer_size;
                     bool buy;
                     bool sell;
public:
                     //--- Class constructors and destructors
                     Strategy(void);
                    ~Strategy(void);
                    
                    //--- Check if we have any valid trading signals from our strategy
                    virtual bool BuySignal(void);
                    virtual bool SellSignal(void);
                    
                    //--- Update the technical indicators in our strategy
                    virtual bool Update(void);
                    
                    //--- Get the size of the technical indicator buffers
                            int  GetIndicatorBufferSize(void);
  };

デフォルトのコンストラクタは、戦略のすべてのインスタンスで共有されるすべてのデフォルト値を設定します。

//+------------------------------------------------------------------+
//| The only way to create an object of the class                    |
//+------------------------------------------------------------------+
Strategy::Strategy(void)
  {
      //--- Upon initialization, both flags should be false 
      buy = false;
      sell = false;
      buffer_size = 10;
  }

一般的にゲッターとセッターと呼ばれるいくつかのユーティリティメソッドが必要になります。まず、戦略で使用されるインジケーターに対して選択した現在のバッファサイズを返すメソッドを定義します。

//+------------------------------------------------------------------+
//| The size of our indicator buffer                                 |
//+------------------------------------------------------------------+
int Strategy::GetIndicatorBufferSize(void)
   {
      int res = buffer_size;
      return(res);
   }

さらに、各戦略には2つのメソッドを実装する必要があります。それぞれが、買いシグナルまたは売りシグナルが存在するかどうかを判断し、結果を返す役割を担います。基底クラスを継承する各クラスは、自身のエントリールールを定義し、このメソッドを実装しなければなりません。子クラスでオーバーライドされない場合、親クラスは常にfalseを返し、開発者に対してこのメソッドを子クラスで上書きするよう指示します。

//+------------------------------------------------------------------+
//| Check if our strategy is giving us any buy signals               |
//+------------------------------------------------------------------+
bool Strategy::BuySignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }
   
//+------------------------------------------------------------------+
//| Check if our strategy is giving us any sell signals              |
//+------------------------------------------------------------------+
bool Strategy::SellSignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

戦略オブジェクトを更新すると、取引に使用されている戦略パラメータも更新されます。

//+------------------------------------------------------------------+
//| Update our strategy parameters                                   |
//+------------------------------------------------------------------+
bool Strategy::Update(void)
   {
      //--- The user is intended to overwrite the function in the child class
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

現時点では、クラスのデストラクタは空です。

//+------------------------------------------------------------------+
//| The class destructor is currently empty                          |
//+------------------------------------------------------------------+
Strategy::~Strategy(void)
  {
  }
//+------------------------------------------------------------------+

まずクラスの本体を定義します。このクラスは「OpenCloseMACrossover」と名付けられています。本戦略は2本の移動平均インジケーターに基づき、それぞれ同一の期間を設定しつつ、始値系列と終値系列に適用する仕組みです。親クラスでvirtualとして定義されていたメソッドは、この子クラスにおいても再びvirtualとして宣言されます。

売りシグナルは、始値に基づく移動平均が終値に基づく移動平均を上回ったときに生成されます。逆に、買いシグナルはその反対の状況で発生します。ここでの考え方は、終値の平均が始値の平均より大きい場合、価格推移は強気と見なされ、その方向に強いトレンドが持続する可能性が高いというものです。

//+------------------------------------------------------------------+
//|                                         OpenCloseMACrossover.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"

#include<VolatilityDoctor\Strategies\Parent\Strategy.mqh>
#include<VolatilityDoctor\Indicators\MA.mqh>

class OpenCloseMACrossover : public Strategy
  {
private:
                     //--- Create 2 moving average instances
                     MA *ma_array[2];

public:
                     //---- Class constructors and destructor
                     OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode);
                     ~OpenCloseMACrossover();
                     
                     //--- Class methods
                     virtual bool Update(void);
                     virtual bool BuySignal(void);
                     virtual bool SellSignal(void);
                    
  };

すでに取引戦略のルールについては確認済みですので、これらの条件をチェックするメソッドの実装は、私たちにとって簡単におこなえる作業です。

//+------------------------------------------------------------------+
//| Check For a Buy Signal                                           |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::BuySignal(void)
   {
      //--- Our buy signal is generated if the close moving average is above the open.
      return(ma_array[0].GetCurrentReading()>ma_array[1].GetCurrentReading());
   }

//+------------------------------------------------------------------+
//| Check For a Sell Signal                                          |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::SellSignal(void)
   {
      //--- Our sell signal is generated if the open moving average is above the close.
      return(ma_array[0].GetCurrentReading()<ma_array[1].GetCurrentReading());
   }

更新メソッドは、SingleBufferIndicatorクラス用に作成したIndicatorの更新関数を呼び出します。このメソッドでは、バッファサイズをパラメータとして渡す必要があります。親クラスに、バッファサイズを返すメソッドを作成しており、それを利用しています。親クラスへの参照は、Strategy::GetIndicatorBufferSize()のようにダブルコロン「::」の構文を使っておこないます。最後に、更新メソッドは更新された値が0でないことを確認した上で、呼び出し元のコンテキストに制御を戻します。

//+------------------------------------------------------------------+
//| Our update method                                                |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::Update(void)
   {
      //--- Copy indicator readings 
      //--- We will always get the buffer size from the parent class
      ma_array[0].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      ma_array[1].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      
      //--- Make sure neither of the indicator values equal 0
      if((ma_array[0].GetCurrentReading() * ma_array[1].GetCurrentReading()) != 0) return(true);
      
      //--- If one/both indicator values equal 0, something went wrong.
      return(false);
   }

クラスのコンストラクタでは、移動平均インジケーターオブジェクトの新しいインスタンスを動的に2つ生成し、それらのポインタを同じ型の配列に格納します。この配列は、私たちが独自に定義したMA型のポインタ型です。

//+------------------------------------------------------------------+
//| Our class constructor                                            |
//+------------------------------------------------------------------+
OpenCloseMACrossover::OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode)
  {
      //--- Create two instances of our moving average indiator objects
      ma_array[0] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_CLOSE);
      ma_array[1] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_OPEN);
      
      //--- Give feedback
      Print("Strategy class loaded correctly");
  }

クラスデストラクタは、作成した動的オブジェクトを削除し、消費メモリの管理に役立ちます。

//+------------------------------------------------------------------+
//| Our class destructor                                             |
//+------------------------------------------------------------------+
OpenCloseMACrossover::~OpenCloseMACrossover()
  {
   //--- Delete the custom objects we made
   delete ma_array[0];
   delete ma_array[1];
   
   //--- Give feedback
   Print("Strategy deinitialized correctly. Goodbye");
  }
//+------------------------------------------------------------------+

では、最初の戦略クラスのテストに進みます。最終的なアプリケーションでは、3つのクラス、つまり3種類の異なる戦略を実行する予定です。そのため、優れた開発者として、それぞれのクラスを個別にテストする必要があります。テストは、あらかじめハードコーディングされた同一の戦略と比較しておこないます。両戦略が同じテスト結果を返す場合、テストは合格とみなされます。同じ期間でバックテストをおこなうことで、この手法は将来的に数時間に及ぶバグ探索の手間を省くことができます。

まず、両方のテストで共通に使用するシステム定数を定義します。両戦略が等価であれば、同じタイミングでトリガーされ、同じ数のポジションを開き、買い:売りの比率も同じになるはずです。これらのシステム定数を繰り返し定義するのは意図的なテストの一部であり、これらの定数が戦略のパラメータを制御しているためです。

//+------------------------------------------------------------------+
//|                                                   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"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

次に、依存ファイルを読み込みます。

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

次に、テクニカル指標を制御し、ポジションがオープンしている期間をカウントするためのグローバル変数がいくつか必要になります。

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

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

//--- System Types
double ma_open[],ma_close[];
int    ma_open_handler,ma_close_handler;
intn    position_timer; 

システムが初期化されると、テクニカル指標が読み込まれ、正常であることが検証されます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Our technical indicators
   ma_close_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_CLOSE);
   ma_open_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_OPEN);

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


//--- Safety checks
   if(ma_close_handler == INVALID_HANDLE)
      return(false);
   if(ma_open_handler == INVALID_HANDLE)
      return(false);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

システムが使用されなくなった場合は、読み込んだテクニカル指標を解放し、セットアップ手順中に作成した動的オブジェクトを削除します。

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

   delete TradeTime;
   delete TradeInformation;

  }
//--- End of Deinit Scope

ターミナルが新しい価格レベルを受け取った場合、まず新しいローソク足が形成されたかどうかを確認します。この場合、テクニカル指標を常に更新し、次にポジションが開いているかどうかを確認します。開いているポジションがない場合は取引シグナルをチェックし、満期まで待ってからポジションをクローズします。

//+------------------------------------------------------------------+
//| 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 our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Call the CopyBuffer method to get updated indicator values
   CopyBuffer(ma_close_handler,0,0,1,ma_close);
   CopyBuffer(ma_open_handler,0,0,1,ma_open);
  }
//--- End of Update Scope

CheckSignal関数は、前に定義した取引条件を検索します。価格変動が強気であると判断するには、終値移動平均が始値移動平均より上にある必要があります。

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(ma_close[0] > ma_open[0])
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short
   else
      if(ma_close[0] < ma_open[0])
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }

  }
//--- End of CheckSignal Scope

最後に、プログラムの最後に作成したシステム変数を常に未定義にします。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

それでは、クラスのパフォーマンスのベースラインレベルを確立しましょう。 

図1:ベースラインレベルの初期テスト設定

次のステップは、バックテストの開始点と終了点を定義することです。もしよろしければ、日足の時間軸を選択して進めます。

図2:ベースラインレベルに使用するテストの日付

ハードコーディングされたバージョンの戦略によって生成された資産曲線は、両戦略を同じパラメータで実行するように設定すれば、実装したクラスから取得することができます。それでは、クラスを正しくエラーなく実装できているか確認してみましょう。 

図3:ベースライン戦略によって確立された資産曲線

両方のテストの正確な統計的詳細は完全には一致しないことに注意してください。バックテストではランダムな遅延やシステマティックなノイズをシミュレートしているため、結果は完全に同一ではなく、非常に近い値であることを確認することを目指します。

図4:ハードコーディングされた移動平均クロスオーバー戦略を用いたバックテストの詳細結果

一貫性を保つため、このクラステストでも先に定義したシステム定数をそのまま使用します。

//+------------------------------------------------------------------+
//|                                                   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"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

次に、依存ファイルを読み込みます。

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

戦略インスタンス用に新しいグローバル変数を作成する必要があります。

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

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

//--- System Types
int    position_timer;

ほとんどの場合、アプリケーションの大部分は変更されていません。今回の目的は、クラスによって生成されるシグナルの影響を切り分けることです。そのため、今回のコードの大部分は、読者にとって、最初に実施したテストと同じように馴染みのある内容になっているはずです。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);
   MACross          = new OpenCloseMACrossover(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);

//--- 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;
  }
//--- End of Deinit Scope

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {
      //--- Update strategy
      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 our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Update the strategy
   MACross.Update();
  }
//--- End of Update Scope

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(MACross.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;
        }
  }
//--- End of CheckSignal Scope

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

これで、MQL5 MAクロスオーバー戦略クラスのテストを始める準備が整いました。まず、クラスを実行するエキスパートアドバイザー(EA)を読み込み、最初のテストで使用したものと同じバックテストの日付を設定します。

図5:戦略を評価する際に使用する初期の日付(今後おこなうフォワードテストによって影響を受ける)

選択した設定が最初に使用した設定と一致していることを確認してください。

図6:堅牢なバックテストのために実ティックとランダム遅延を選択する

クラスとハードコーディングされた戦略の詳細なテスト結果はほぼ同一で、両戦略は12回の取引をおこない、買い・売りの比率も同じであり、シャープレシオも互いに近い値となりました。

図7:戦略クラスによって生成された詳細な統計は、ハードコードされた取引戦略から得られた結果と一致する

クラスによって生成された資産曲線は、図3によく似ています。したがって、2つの戦略は予想どおり一致しています。

図8:クラスによって生成された資産曲線はハードコードされた戦略と一致する

クラスが正しく実装されていることを確認できたので、これで最適な戦略パラメータの探索を開始できます。 

まず、ほとんどのシステム定数をユーザー入力に変更する必要があります。これにより、遺伝的最適化ツールが戦略の調整をおこなえるようになります。そのため、システム定義は一つだけで十分です。

//+------------------------------------------------------------------+
//|                                                   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                                                 |
//+------------------------------------------------------------------+
#define MA_SHIFT 0

//+------------------------------------------------------------------+
//| User Inputs                                                      |
//+------------------------------------------------------------------+
input   group          "Strategy Parameters"
input   int             MA_PERIOD      =        10;//Moving Average Period
input   int             HOLDING_PERIOD =         5;//Position Holding Period
input   ENUM_TIMEFRAMES MA_TIME_FRAME  = PERIOD_D1;//Moving Average Time Frame
input   ENUM_MA_METHOD  MA_TYPE        =  MODE_EMA;//Moving Average Type

システムの残りの部分はほとんど変更されていないため、MetaTrader 5のバックテストとフォワードテストの違いについて説明を始めましょう。

バックテストとは、簡単に言えば、過去のデータを用いて取引戦略を実行することです。しかし、データを単純に過去検証に使うだけでなく、データを分割して一部を戦略パラメータの探索に使用し、残りのデータでそのパラメータを検証することも可能です。

これがフォワードテストの利点です。単に良い設定を見つけるだけでなく、その設定がどれだけ安定しているかを確認することができます。

そのため、データの50%を学習用に、残り50%をテスト用に使用するよう、[Forward]パラメータを1/2に設定します。

図9:[Forward]フィールドを1/2に設定して、データの半分を学習、残りをテストに使用する

MetaTrader 5ターミナルには、さまざまな最適化手法が用意されています。ここでは[高速遺伝的アルゴリズム(速い)]を選択します。これはシステム負荷が少なく、それでいて信頼性のある結果を得られるためです。

図10:各テストの後に新しい戦略パラメータを生成するための最適化ツールが必要

テストが開始されると、下図(図11)のように散布図で結果が表示されます。このプロットにより、各学習反復でのパフォーマンスを視覚的に確認できます。オプティマイザはデータの前半のみを参照し、その結果をもとに次の反復で試すパラメータを学習します。

図11:高速遺伝的オプティマイザを使用して、戦略のシャープレシオを向上させる。各点はテスト反復の結果を表している

この散布図は右クリックで操作でき、プロットする軸を変更したり、結果を3D表示にすることも可能です。

図12:コンテキストメニューを使用して、プロットの軸を変更したり異なる関係性を探索したりできる

データを異なる形式で可視化することで、戦略と市場との関係性をより詳しく観察できます。たとえば、高い時間軸を選ぶことで、戦略の利益が向上する傾向が見られます。

図13:3Dバーグラフで、高時間軸ほどシャープレシオが改善することを確認する

ストラテジーテスターは、入力パラメータの各組み合わせにおける詳細な結果も提供します。表下部のパネルでは、Back testとForwardが分かれています。 

図14:  MetaTrader 5は、試行したすべての戦略パラメータに対する詳細な分析も提供する

テーブルを右クリックすると、コンテキストメニューが表示され、多くの便利な操作が可能です。列の表示選択や、Forward結果のファイルへのエクスポートなどもおこなうことができます。

図15:結果表のコンテキストメニューから、XML形式で結果をエクスポートできる

バックテストとフォワードテストの結果を個別に詳細確認できます。下図(図16)はバックテスト結果です。私たちが特に注目するのは、フォワード結果がバックテストからどれだけ逸脱しているかです。

図16:バックテストの詳細統計結果(遺伝的オプティマイザの結果)

フォワード結果はバックテスト結果に近似しています。これは設定の安定性の良い指標です。逆に、フォワード結果がバックテストと大きく異なる場合は、戦略の不安定性を示しています。

図17:特に注目すべきフォワード結果

両テストで得られた資産曲線も提供されます。中央の長い縦線は、バックテストとフォワードテストの境界を示しています。

図18:学習時およびフォワードテスト時の資産曲線は共に上昇傾向を示す

最後に、遺伝的オプティマイザが今回見つけた最適戦略設定です。

図19:遺伝的アルゴリズム検索によって得られた最適な設定


結論

この記事では、MetaTrader 5ストラテジーテスターの価値を説明しました。クラスを設計する予定がない読者でも、MQL5に組み込まれたオブジェクト指向プログラミング(OOP)の原則を開発プロセスに統合するための実用的な使用例を得ることができます。また、本記事は、信頼性の高いクラスを作成・テストするための良い開発手法を採用することを推奨しています。

最後に、ここで解説したOOP設計原則を活用することで、読者は戦略を確実に構築し、異なる銘柄や時間軸において最適なパラメータを簡単にテストすることができます。次回のディスカッションでは、RSIと移動平均の戦略を組み合わせる方法について紹介します。

ファイル名 ファイルの説明
MSA Test 1 Baseline.mq5 クラスが模倣すべき、クロスオーバー戦略のハードコーディング版
MSA Test 1 Class.mq5 移動平均戦略クラスのテスト用ファイル
MSA Test 1.mq5 MetaTrader 5ストラテジーテスターを用いて最適な戦略パラメータを探索するEA
OpenCloseMACrossover.mqh 移動平均戦略を実装したクラス
Strategy.mqh すべての戦略の基底クラス
MSA Test 1.ex5 EAのコンパイル済みバージョン

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

添付されたファイル |
MSA_Test_1.mq5 (5.28 KB)
Strategy.mqh (3.98 KB)
MSA_Test_1.ex5 (44.16 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5で取引管理者パネルを作成する(第12回):FX取引計算ツールの統合 MQL5で取引管理者パネルを作成する(第12回):FX取引計算ツールの統合
取引において重要な数値を正確に計算することは、すべてのトレーダーにとって欠かせません。本記事では、強力なユーティリティであるFX取引計算ツールを取引管理パネルに組み込み、マルチパネル型の取引管理者システムの機能をさらに拡張する方法について解説します。リスク、ポジションサイズ、潜在的な利益を効率的に算出することは、取引の精度を高めるうえで非常に重要です。この新機能は、パネル内でこれらの計算をよりスムーズかつ直感的におこなえるよう設計されています。本記事では、MQL5を用いた高度な取引パネル構築の実践的な応用例を紹介します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
知っておくべきMQL5ウィザードのテクニック(第69回):SARとRVIのパターンの使用 知っておくべきMQL5ウィザードのテクニック(第69回):SARとRVIのパターンの使用
パラボリックSAR (SAR)と相対活力指数(RVI)は、MQL5のエキスパートアドバイザー(EA)内で併用可能なもう一つのインジケーターペアです。このインジケーターペアは、これまでに取り上げたものと同様に補完的で、SARはトレンドを定義し、RVIはモメンタムを確認します。通常通り、MQL5ウィザードを使用してこのインジケーターペアリングを構築し、その可能性をテストします。