English Русский 中文 Español Português
preview
多通貨エキスパートアドバイザーの開発(第23回):自動プロジェクト最適化段階のコンベアの配置(II)

多通貨エキスパートアドバイザーの開発(第23回):自動プロジェクト最適化段階のコンベアの配置(II)

MetaTrader 5テスター |
9 0
Yuriy Bykov
Yuriy Bykov

はじめに

本連載の以前の記事の1つ(第20回)では、すでにこの問題に焦点を当てました。これにより、プロジェクトの今後の発展に向けて、より適切な方向性を選択することができました。最適化データベース内で単一プロジェクトとして実行されるすべてのタスクを手動で作成する代わりに、より便利なツールを得ることができました。それが最適化プロジェクト作成スクリプトです。より正確には、これはさまざまな取引戦略の最適化プロジェクト作成に容易に適応できるテンプレートです。

この記事では、作成した最適化プロジェクトを起動し、選択された新しい取引戦略グループを直接新しいデータベースにエクスポートできる、完全に機能するソリューションを紹介します。このデータベースは、これまで使用してきた最適化データベース(完全版および簡易版)と区別するため、EAデータベースと名付けました。EAデータベースは、再コンパイルをおこなうことなく、使用する取引システムの設定を更新することで、取引口座上で動作する任意の最終EAから利用できます。この仕組みの正確性についてはまだテストが必要ですが、このアプローチによってコンベア全体の運用が簡素化されることはすでに明らかです。これまでは、既存の3つのコンベアステージに加えて、さらにいくつかのステージを追加する予定でした。

  • ライブラリをエクスポートして、データフォルダにExportedGroupsLibrary.mqhを取得します(Stage4)。
  • ファイルを作業フォルダにコピーする(Stage5、PythonまたはDLL)、または前段階を修正して作業フォルダに直接エクスポートします。
  • 最終EAをコンパイルします(Stage6、Python)。
  • 最終EAの新しいバージョンでターミナルを起動します。

現在では、これらのステージは不要になりました。同時に、大きな欠点も解消されました。それは、ストラテジーテスター内でこのような自動更新メカニズムの正しい動作を確認できないという問題です。再コンパイルのステージが存在すると、1回のテスターパス中にEAのコンパイル済みコードを変更できないという制約と両立しません。

そして最も重要なのは、任意の戦略を最適化するために書かれたすべてのコードをより簡単に利用できるようにするための重要な一歩を踏み出すことです。本記事では、具体的なステップごとのアルゴリズムを説明していきます。


道筋の整理

まずは、プロジェクトのファイル構造に対する長らく遅れていた変更を実装します。現在、すべてのコードは1つのフォルダにまとめられています。一方で、これにより新しいプロジェクトにコードを移行したり使用したりするのは簡単ですが、他方で継続的な開発の過程では、異なる取引戦略ごとにほぼ同一の作業用プロジェクトフォルダが複数できてしまい、それぞれを個別に更新する必要があります。そこで、すべてのコードを「ライブラリ部分」と「プロジェクト部分」に分けます。ライブラリ部分はすべてのプロジェクトで共通とし、プロジェクト部分にはそれぞれのプロジェクト固有のコードを含めます。

次に、最終EAの運用中に新しい戦略グループが現れた場合でも、更新されたパラメータを正しく読み込み、作業を継続できるかどうかを確認する機能を実装します。通常どおり、まずはストラテジーテスターでEAを動かし、期待する動作をモデル化して確認します。テスターでの結果が満足できるものであれば、テスターで動作しなくなった最終EAで使用することが可能になります。

これには何が必要でしょうか。前のセクションでは、最適化区間の終了日や最適化コンベアの実行完了情報をEAデータベースに保存する機能を実装していませんでした。これがないと、テスターを実行した際に、最終EAはその戦略グループが特定のシミュレーション日付ですでに形成されているかどうかを判定できません。

また、最終EAも、新しい戦略グループがEAデータベースに現れた場合に、自身で再初期化できるように修正する必要があります。現状では、そのような機能はありません。ここでは、現在の取引戦略グループに関する情報を少なくともある程度保持しておくと、1つのグループから別のグループへの移行が成功したかどうかを明確に確認できます。できれば、この情報はEAが動作しているチャート上で直接確認できると便利ですが、もちろん通常のターミナルログ出力を使用しても構いません。

最後に、これまでに開発したツールを使った作業の全体的なアルゴリズムの説明を提供します。

では始めましょう。


異なるファイル構造への移行

これまでのすべてのパートでは、開発は1つの作業用プロジェクトフォルダ内でおこなわれていました。既存のファイルはその中で修正され、新しいファイルが随時追加されていました。時には、プロジェクトにもう不要になったファイルが削除されたり「忘れられたり」することもありました。この方法は、単一の取引戦略(たとえば、記事で例として使われたSimpleVolumes戦略)で作業する場合には合理的でした。しかし、自動最適化機構を他の取引戦略に拡張する場合、プロジェクト作業フォルダの完全なコピーを作成し、その中の一部のファイルだけを変更する必要がありました。

このように接続された取引戦略の数(および異なる作業フォルダの数)が増えると、すべてのフォルダのコードを最新の状態に保つ作業はますます手間がかかるようになりました。そこで、すべての作業フォルダで共通となるコード部分を、MQL5/Include内のライブラリフォルダに移動することにしました。ライブラリファイルの共有フォルダ名はAdvisorと設定し、既存の同名フォルダと衝突しないようにユニークなコンポーネントを追加しました。これにより、ライブラリファイルはMQL5/Include/antekov/Advisor/に配置されます。

すべてのファイルを移動した後、さらに整理を進めました。ファイルを、内部のファイルの共通目的に応じたサブフォルダに分けることにしました。これにより、ファイルを他のファイルにインクルードするディレクティブの修正が必要になりましたが、最終的にはEAとライブラリファイルの両方を個別に正常にコンパイルできる状態になりました。

以下が、変更後のファイル構造です。

図1:Advisorライブラリのファイル構造

ご覧の通り、いくつかのファイルグループを選択し、以下のサブフォルダに配置しました。

  • Base: 他のプロジェクトクラスが継承する基本クラス
  • Database:プロジェクトEAで使用されるすべての種類のデータベースを扱うファイル
  • Experts:異なるタイプのEAで使用される共通部分のファイル
  • Optimization:自動最適化を担当するクラス
  • Strategies:プロジェクトの動作を示すために使用される取引戦略の例
  • Utils:補助ユーティリティ、コード削減用マクロ
  • Virtual:仮想の取引注文やポジションのシステムを用いた各種オブジェクト作成用クラス

上記サブフォルダのうち、Expertsフォルダだけは特に注目に値します。図1のファイル構成と前回の構成を比較すると、このフォルダだけが新規のファイルを含んでいることがわかります。最初は、既存EAのファイルを部分的に名前変更してここに移しただけのように思うかもしれませんが、拡張子は*.mq5ではなく*.mqhです。では、その前に、特定の取引戦略用の個別プロジェクトフォルダに残るものを確認しましょう。

MQL5/Experts/内にプロジェクト用の個別フォルダを作成し、任意の名前を付けます。その中には、使用するすべてのEAファイルが含まれます。

図2:EAライブラリを利用するプロジェクトのファイル構造

これらのファイルの目的は次のとおりです。

  • CreateProject.mq5:最適化データベース内の自動最適化プロジェクトを作成するEA。データベース内の各プロジェクトは、1つ以上のタスクからなる3つのステージで表現されます。各タスクはステージEAによって実行される1つ以上の最適化タスクで構成されます。

  • HistoryReceiverExpert.mq5:以前保存した取引履歴を再現するEA。長らく使用していません。ブローカー変更時の結果の再現性を確認する目的で作成されたもので、自動最適化の動作には必須ではないため、不要であれば削除可能です。

  • Optimization.mq5:自動最適化プロジェクトのタスクを実行するEA。これらのタスクを順次実行するプロセスを「自動最適化コンベア」と呼びます。

  • SimpleVolumes.mq5:複数のSimpleVolumes型取引戦略を組み合わせた最終EA。EAデータベースからこれらの構成情報を取得します。この情報は、自動最適化コンベアの第3ステージEAによってEAデータベースに保存されます。

  • Stage1.mq5:自動最適化コンベアの第1ステージEA。単一インスタンスの戦略を最適化します。

  • Stage2.mq5:第2ステージEA。第1ステージで得られた多数の良好な単一インスタンスから、通常8または16の少数グループを選択し、統一利益に基づき最良の結果を示すグループを選びます。

  • Stage3.mq5:自動最適化コンベアの第3ステージEA。 第2ステージで得られたすべてのグループを結合し、ポジションサイズを正規化して、設定で指定されたスケーリングファクター付きでEAデータベースに保存します。

リストされたEAファイルのうち、CreateProject.mq5だけが内容を保持しています。他のEAは基本的にMQL5/Include/antekov/Advisor/Experts内にあるAdvisorライブラリの対応する*.mqhファイルをインクルードする命令のみを含みます。特に、SimpleVolumes.mq5HistoryReceiverExpert.mq5は同じExpert.mqhインクルードファイルを使用します。実際には、第2・第3ステージEAで異なる戦略ごとに別のコードを書く必要はありません。第1ステージEAの場合、唯一の違いは入力値の違いと、それらの値から生成される初期化文字列の作成です。それ以外は共通です。他のすべても同じになります。

したがって、CreateProject.mq5のみが、別の戦略用プロジェクトに切り替える際に大幅な修正を必要とします。将来的には、この共通部分も抽出する予定です。

次に、最終EAの自動更新を実装するために、ライブラリファイルにどのような変更が必要かを見ていきましょう。


完了日

前回のパートでは、ほぼ同一の4つの最適化プロジェクトを起動したことを思い出してください。これらの違いは、単一の取引戦略インスタンスに対する最適化区間の終了日のみでした。取引銘柄の構成、時間足、その他のパラメータはすべて同一でした。その結果、EAデータベースのstrategy_groupsテーブルには次のようなレコードが作成されました。

グループ名に最適化区間の終了日を追加したことで、どのグループがどの終了日に対応しているかを人間は理解できます。しかし、EA自身もこの情報を理解できなければなりません。そのため、この日付を保存するためのフィールドを、このテーブルに2つ用意していました。これらのフィールドは、レコード作成時に必ず値が設定される必要があります。また、その処理を実装するためのコード上の場所も、すでに用意してあります。

//+------------------------------------------------------------------+
//| Export an array of strategies to the specified EA database       |
//| as a new group of strategies                                     |
//+------------------------------------------------------------------+
void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) {
// Connect to the required EA database
   if(DB::Connect(p_advFileName, DB_TYPE_ADV)) {
      string fromDate = "";   // Start date of the optimization interval
      string toDate = "";     // End date of the optimization interval

      // Create an entry for a new strategy group
      string query = StringFormat("INSERT INTO strategy_groups VALUES(NULL, '%s', '%s', '%s', NULL)"
                                  " RETURNING rowid;",
                                  p_groupName, fromDate, toDate);
      ulong groupId = DB::Insert(query);

      // ...
   }
}

最適化区間の開始日と終了日は、データベースのstagesテーブルに保存されています。そのため、このコードの箇所で対応するSQLクエリを実行すれば、そこから取得することも可能です。しかし、この方法は最適とは言えませんでした。というのも、これらの日付情報を含めた各種情報を取得するためのSQLクエリを実行するコードは、すでに実装されていたからです。この処理は、自動最適化EA内で実行されており、データベースから次に実行すべき最適化タスクに関する情報を取得するためのものでした。この情報には、今回必要としている日付情報も含まれている必要があります。そこで、この既存の仕組みを活用することにします。 

そのために、まずCOptimizerTaskクラスのオブジェクトを作成します。コンストラクタには、最適化データベースの名前を渡します。この名前は、CTesterHandler::s_fileNameというstaticクラスフィールドに格納されています。また、CTesterHandler::s_idTaskという別のstaticフィールドには、現在の最適化タスクIDが格納されています。これを、最適化タスクデータを読み込むためのメソッドに渡します。これらの処理が完了すると、必要な開始日および終了日は、タスクオブジェクトのm_params構造体内の対応するフィールドから取得できるようになります。

//+------------------------------------------------------------------+
//| Export an array of strategies to the specified EA database       |
//| as a new group of strategies                                     |
//+------------------------------------------------------------------+
void CTesterHandler::Export(CStrategy* &p_strategies[], string p_groupName, string p_advFileName) {
// Create an optimization task object
   COptimizerTask task(s_fileName);
// Load the data of the current optimization task into it
   task.Load(CTesterHandler::s_idTask);

// Connect to the required EA database
   if(DB::Connect(p_advFileName, DB_TYPE_ADV)) {
      string fromDate = task.m_params.from_date; // Start date of the optimization interval
      string toDate = task.m_params.to_date;     // End date of the optimization interval

      // Create an entry for a new strategy group
      string query = StringFormat("INSERT INTO strategy_groups VALUES(NULL, '%s', '%s', '%s', NULL)"
                                  " RETURNING rowid;",
                                  p_groupName, fromDate, toDate);
      ulong groupId = DB::Insert(query);

      // ...
   }
}

Virtualサブフォルダ内のライブラリにあるTesterHandler.mqhファイルに加えた変更を保存しましょう。

次に、CreateProject.ex5 EAを使用して、いくつかのプロジェクトを再作成します。処理を高速化するため、最適化区間は短く(4か月)設定します。各プロジェクトごとに、最適化区間の開始日と終了日を1か月ずつ前にずらします。その結果、次のようになります。

ご覧のとおり、EAデータベース内の各グループには、最適化区間の終了日が含まれるようになりました。なお、この日付は第3ステージのタスクの区間から取得されています。すべてが正しく機能するためには、3つすべてのステージにおける最適化区間の日付が同一である必要があります。これは、プロジェクト作成EA内で保証されています。


最終EAの修正

最終EAで使用される戦略グループの自動更新を実装する前に、新しいプロジェクトファイル構造への移行によって生じた変更点を確認しておきましょう。すでに述べたとおり、最終EAは現在2つのファイルから構成されています。メインファイルはプロジェクトフォルダ内に配置されており、SimpleVolumes.mq5という名前です。完全なコードは次のとおりです。

//+------------------------------------------------------------------+
//|                                                SimpleVolumes.mq5 |
//|                                 Copyright 2024-2025, Yuriy Bykov |
//|                            https://www.mql5.com/ja/users/antekov |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024-2025, Yuriy Bykov"
#property link      "https://www.mql5.com/ja/articles/16913"
#property description "The final EA, combining multiple instances of trading strategies:"
#property description " "
#property description "Strategies open a market or pending order when,"
#property description "the candle tick volume exceeds the average volume in the direction of the current candle."
#property description "If orders have not yet turned into positions, they are deleted at expiration time."
#property description "Open positions are closed only by SL or TP."
#property version "1.22"

#include <antekov/Advisor/Experts/Expert.mqh>

//+------------------------------------------------------------------+

このコードには、実質的に最終EA用のライブラリファイルをインポートする1つの命令しか含まれていません。これは、「何が書かれているか」よりも、「何が書かれていないか」の方が重要になる、まさに典型的なケースです。それでは、これを2つ目のEAであるHistoryReceiverExpert.mq5のコードと比較してみましょう。

//+------------------------------------------------------------------+
//|                                        HistoryReceiverExpert.mq5 |
//|                                 Copyright 2024-2025, Yuriy Bykov |
//|                            https://www.mql5.com/ja/users/antekov |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024-2025, Yuriy Bykov"
#property link      "https://www.mql5.com/ja/articles/16913"
#property description "The EA opens a market or pending order when,"
#property description "the candle tick volume exceeds the average volume in the direction of the current candle."
#property description "If orders have not yet turned into positions, they are deleted at expiration time."
#property description "Open positions are closed only by SL or TP."
#property version "1.01"

//+------------------------------------------------------------------+
//| Declare the name of the final EA.                                |
//| During the compilation, the function of generating               |
//| the initialization string from the current file will be used     |
//+------------------------------------------------------------------+
#define  __NAME__ MQLInfoString(MQL_PROGRAM_NAME)

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Testing the deal history"
input string historyFileName_    = "SimpleVolumesExpert.1.19 [2021.01.01 - 2022.12.30]"
                                   " [10000, 34518, 1294, 3.75].history.csv";    // File with history

//+------------------------------------------------------------------+
//| Function for generating the strategy initialization string       |
//| from the inputs                                                  |
//+------------------------------------------------------------------+
string GetStrategyParams() {
   return StringFormat("class CHistoryStrategy(\"%s\")\n", historyFileName_);
}

#include <antekov/Advisor/Experts/Expert.mqh>

//+------------------------------------------------------------------+

SimpleVolumes.mq5ファイルには含まれていない3つのブロックが色でハイライトされています。これらの存在は、最終EAのExperts/Expert.mqhライブラリファイルで考慮されています。最終EAの名前を定数として指定していない場合、初期化文字列を生成する関数が宣言され、EAのデータベースからその文字列を取得します。名前が指定されている場合は、そのような関数はライブラリファイルがインクルードされる親ファイル内で宣言される必要があります。

// If the constant with the name of the final EA is not specified, then
#ifndef __NAME__
// Set it equal to the name of the EA file 
#define  __NAME__ MQLInfoString(MQL_PROGRAM_NAME)

//+------------------------------------------------------------------+
//| Function for generating the strategy initialization string       |
//| from the default inputs (if no name was specified).              | 
//| Import the initialization string from the EA database            |
//| by the strategy group ID                                         |
//+------------------------------------------------------------------+
string GetStrategyParams() {
// Take the initialization string from the new library for the selected group
// (from the EA database)
   string strategiesParams = CVirtualAdvisor::Import(
                                CVirtualAdvisor::FileName(__NAME__, magic_),
                                groupId_
                             );

// If the strategy group from the library is not specified, then we interrupt the operation
   if(strategiesParams == NULL && useAutoUpdate_) {
      strategiesParams = "";
   }

   return strategiesParams;
}
#endif

次に、Experts/Expert.mqhライブラリファイルのEA初期化関数内で、初期化文字列生成関数の可能なオプションのひとつが使用されます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
// ...

// Initialization string with strategy parameter sets
   string strategiesParams = NULL;

// Take the initialization string from the new library for the selected group
// (from the EA database)
   strategiesParams = GetStrategyParams();

// If the strategy group from the library is not specified, then we interrupt the operation
   if(strategiesParams == NULL) {
      return INIT_FAILED;
   }

// ...

// Successful initialization
   return(INIT_SUCCEEDED);
}

したがって、必要であれば、EAデータベースから初期化文字列を読み込まない最終EAを作成することも可能です。その場合は、*.mq5ファイル内で__NAME__定数と署名関数を宣言します。 

string GetStrategyParams()

これで、自動更新の実装に進むことができます。


自動更新

自動更新を実装する最初の方法は、実装としては洗練されていない部分もありますが、まずは動作することが最重要です。最終EAライブラリファイルに必要な変更は、大きく2つの部分で構成されます。

まず、入力パラメータの構成を少し変更しました。旧ライブラリで使われていた「グループ番号」の列挙型を削除し、EAデータベースから取得する「グループID」に置き換え、自動更新を有効化する論理型パラメータを追加しました。

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Use a strategy group"
sinput int        groupId_       = 0;     // - ID of the group from the new library (0 - last)
sinput bool       useAutoUpdate_ = true;  // - Use auto update?

input group "::: Money management"
sinput double expectedDrawdown_  = 10;    // - Maximum risk (%)
sinput double fixedBalance_      = 10000; // - Used deposit (0 - use all) in the account currency
input  double scale_             = 1.00;  // - Group scaling multiplier

// ...

次に、ハイライトされた文字列の後に、新しいティック処理関数に以下のコードを追加しました。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   expert.Tick();

// If both are executed at the same time:
   if(groupId_ == 0                       // - no specific group ID specified
         && useAutoUpdate_                // - auto update enabled
         && IsNewBar(Symbol(), PERIOD_D1) // - a new day has arrived
         && expert.CheckUpdate()          // - a new group of strategies discovered
     ) {
      // Save the current EA state
      expert.Save();

      // Delete the EA object
      delete expert;

      // Call the EA initialization function to load a new strategy group 
      OnInit();
   }
}

したがって、自動更新はgroupId_=0かつuseAutoUpdate_=trueの場合にのみ機能します。もし非ゼロのグループIDを指定した場合、そのグループがテスト期間全体で使用されます。この場合、最終EAが取引をおこなうタイミングに制限はありません。

自動更新が有効な場合、生成されたEAはEAデータベースに存在する最も早いグループの最適化期間終了日以降にのみ取引を実行します。この仕組みは、CVirtualAdvisor::CheckUpdate()クラスの新しいメソッド内で実装されます。

//+------------------------------------------------------------------+
//| Check the presence of a new strategy group in the EA database    |
//+------------------------------------------------------------------+
bool CVirtualAdvisor::CheckUpdate() {
// Request to get strategies of a given group or the last group
   string query = StringFormat("SELECT MAX(id_group) FROM strategy_groups"
                               " WHERE to_date <= '%s'",
                               TimeToString(TimeCurrent(), TIME_DATE));

// Open the EA database
   if(DB::Connect(m_fileName, DB_TYPE_ADV)) {
// Execute the request
      int request = DatabasePrepare(DB::Id(), query);

      // If there is no error
      if(request != INVALID_HANDLE) {
         // Data structure for reading a single string of a query result 
         struct Row {
            int      groupId;
         } row;

         // Read data from the first result string
         while(DatabaseReadBind(request, row)) {
            // Remember the strategy group ID
            // in the static property of the EA class
            return s_groupId < row.groupId;
         }
      } else {
         // Report an error if necessary
         PrintFormat(__FUNCTION__" | ERROR: request \n%s\nfailed with code %d", query, GetLastError());
      }

      // Close the EA database
      DB::Close();
   }

   return false;
}

このメソッドでは、EAデータベースから最適化期間の終了日が現在日付以下である最大のグループIDを取得します。したがって、たとえデータベースにレコードが物理的に存在していても、その出現時刻(=最適化期間終了時刻以上)が、戦略テスターの現在のシミュレーション時刻より未来であれば、使用しているSQLクエリの結果には含まれません。

EAが初期化されると、読み込まれた戦略グループのIDはCVirtualAdvisor::s_groupIdクラスの静的フィールドに格納されます。したがって、EAデータベースから取得したIDと、以前に読み込まれたグループIDを比較することで、新しいグループの出現を検出できます。取得したIDが以前のIDより大きければ、新しいグループが現れたことになります。 

EAデータベースから初期化文字列を取得するメソッドでは、すでにデータベースと直接やり取りしているため、自動更新が有効な場合にはグループのテスト期間終了日に関する同じ条件を使用します。

//+------------------------------------------------------------------+
//| Get the strategy group initialization string                     |
//| from the EA database with the given ID                           |
//+------------------------------------------------------------------+
string CVirtualAdvisor::Import(string p_fileName, int p_groupId = 0) {
   string params[];   // Array for strategy initialization strings

// Request to get strategies of a given group or the last group
   string query = StringFormat("SELECT id_group, params "
                               "  FROM strategies"
                               " WHERE id_group = %s;",
                               (p_groupId > 0 ? (string) p_groupId 
                                : "(SELECT MAX(id_group) FROM strategy_groups WHERE to_date <= '"
                                + TimeToString(TimeCurrent(), TIME_DATE) +
                                "')"));

// Open the EA database
   if(DB::Connect(p_fileName, DB_TYPE_ADV)) {
      // ...
   }

// Strategy group initialization string
   string groupParams = NULL;

// Total number of strategies in the group
   int totalStrategies = ArraySize(params);

// If there are strategies, then
   if(totalStrategies > 0) {
      // Concatenate their initialization strings with commas
      JOIN(params, groupParams, ",");

      // Create a strategy group initialization string
      groupParams = StringFormat("class CVirtualStrategyGroup([%s], %.5f)",
                                 groupParams,
                                 totalStrategies);
   }

// Return the strategy group initialization string
   return groupParams;
}

ここで最後に触れておくべき点は、新しい戦略グループに切り替えた後にEAの状態を読み込むメソッドを追加したことです。ポイントは、新しいグループの戦略はEAデータベースにまだ設定が保存されていないため(Save()メソッドがまだ呼ばれていない)、読み込み時にエラーが報告される可能性があることです。しかし、このエラーは無視する必要があります。

もうひとつの追加点は、古い戦略の仮想ポジションを新しい戦略を読み込んだ直後に閉じる必要があることです。そのためには、古い戦略で使用されていたすべての銘柄に対して銘柄受信オブジェクトを作成すれば十分です。これらのオブジェクトは次のティックで開かれているポジションのボリュームを補正します。もしこれをおこなわなければ、ボリューム補正は新しい戦略で仮想ポジションが開かれたときにのみ実行されます。その場合、新しい戦略が以前使われていたシンボルのいずれかを使用しなくなると、その銘柄上のポジションは開いたまま残ってしまいます。

//+------------------------------------------------------------------+
//| Load status                                                      |
//+------------------------------------------------------------------+
bool CVirtualAdvisor::Load() {
   bool res = true;
   ulong groupId = 0;

// Load status if:
   if(true
// file exists
         && FileIsExist(m_fileName, FILE_COMMON)
// currently, there is no optimization
         && !MQLInfoInteger(MQL_OPTIMIZATION)
// and there is no testing at the moment or there is a visual test at the moment
         && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))
     ) {
      // If the connection to the EA database is established
      if(CStorage::Connect(m_fileName)) {
         // If the last modified time is loaded and less than the current time
         if(CStorage::Get("CVirtualReceiver::s_lastChangeTime", m_lastSaveTime)
               && m_lastSaveTime <= TimeCurrent()) {

            PrintFormat(__FUNCTION__" | LAST SAVE at %s",
                        TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS));

            // If the saved strategy group ID is loaded
            if(CStorage::Get("CVirtualAdvisor::s_groupId", groupId)) {
               // Load all strategies ignoring possible errors
               FOREACH(m_strategies, {
                  res &= ((CVirtualStrategy*) m_strategies[i]).Load();
               });

               if(groupId != s_groupId) {
                  // Actions when launching an EA with a new group of strategies.
                  PrintFormat(__FUNCTION__" | UPDATE Group ID: %I64u -> %I64u", s_groupId, groupId);
                  
                  // Reset a possible error flag when loading strategies
                  res = true;
                  
                  string symbols[]; // Array for symbol names
                  
                  // Get the list of all symbols used by the previous group
                  CStorage::GetSymbols(symbols);
                  
                  // For all symbols, create a symbolic receiver.
                  // This is necessary for the correct closing of virtual positions 
                  // of the old strategy group immediately after loading the new one
                  FOREACH(symbols, m_receiver[symbols[i]]);
               }

               // ...
            }
         } else {
            // If the last modified time is not found or is in the future,
            // then start work from scratch
            PrintFormat(__FUNCTION__" | NO LAST SAVE [%s] - Clear Storage",
                        TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS));
            CStorage::Clear();
            m_lastSaveTime = 0;
         }

         // Close the connection
         CStorage::Close();
      }
   }

   return res;
}

これで、新しい戦略グループの自動読み込み機能の動作確認を開始できます。しかし、最初からうまくいったわけではなく、発生したエラーを修正する必要がありました。たとえば、EAデータベースが突然空になった場合にEAが無限ループに陥ることや、テスト開始日を最初の戦略グループ出現日の前日に設定しただけでテストが開始されないことなどが判明しました。最終的に、発見されたすべてのエラーは修正されました。

それでは、作成したライブラリ全体の使用アルゴリズムと、自動更新の確認結果を見ていきましょう。


EAライブラリを使用するアルゴリズム

今回は、ライブラリに含まれるSimpleVolumesモデル戦略の自動最適化と、最終EAのテスター上での起動についてのアルゴリズムを説明します。

  1. ライブラリをIncludeに設定します(図1)。

  2. プロジェクトフォルダを作成し、EAファイルを転送します(図2)。

  3. 第1ステージEAおよびプロジェクト作成用EAのファイルを変更します。モデル戦略を使用する場合は、すでに最新のため変更不要です。プロジェクトフォルダ内のすべてのEAをコンパイルします。

  4. プロジェクト作成EAを起動し、必要なパラメータを設定します(デフォルトでも可)。
    出力は、共有ターミナルデータフォルダに最適化データベースが作成され、プロジェクト用のタスクが格納されます。プロジェクトの説明には、最適化期間の開始日・終了日などを指定可能です。このステップはあくまでコンベア起動用のタスク作成であり、実際の起動は別のEAでおこないます。

  5. 必要に応じて、パラメータを変更してプロジェクト作成を繰り返します。たとえば、異なる期間での自動最適化プロジェクトを同時に作成できます。

  6. 最適化EAを起動して待機します。最適化EAの起動では、最適化データベースに追加されたすべてのプロジェクトが完了するまで待機します。所要時間は、プロジェクトの数、使用する銘柄数や時間足、テスト/最適化期間の長さ、実装されている取引戦略の複雑さ、さらに使用するテストエージェントの数などに依存します。
    出力として、共有フォルダにEAデータベースファイルが作成され、設定に応じた名前が付けられます。
    EAデータベースには戦略グループが保存されます。

  7. 最終EAを起動しましょう。その名前とマジックナンバーが最適化時に指定したものと一致していることが重要です。一致しない場合、空のEAデータベースを作成し、データが現れるのを待機することになります。最終EAは指定されたIDの戦略グループを読み込むか、IDが0の場合は最後に追加されたグループを読み込みます。自動更新オプションが有効な場合、EAは1日1回、EAデータベース内で日付的に利用可能な新しい戦略グループが現れたかを確認し、新しいグループが出現した場合には、以前使用していたグループを置き換えます。


自動更新のテスト

こうして、異なる終了日でデータベースに追加されたすべてのプロジェクトの最適化が完了すると、複数の構成の異なる戦略グループを含むEAデータベースが完成します。それぞれのグループは最適化期間の終了日も異なります。そして、最終EAは、シミュレーション中の現在時刻がこの新しいグループの最適化期間終了日を超えたときに、データベースから新しい戦略グループを取得できるようになります。

なお、EAパラメータの保存と読み込みは、EAがチャート上で起動されるか、ビジュアルテストモードで動作している場合にのみ機能します。そのため、テスターで自動更新を確認する場合は、必ずビジュアルモードを使用する必要があります。

まず、最終EAをgroupId_=1に指定して起動します。この場合、useAutoUpdate_パラメータの値に関係なく、このグループのみが使用されます。このグループは最適化期間が2022.09.01〜2023.01.01で最適化されているため、テスターは2022.09.01からメイン期間を開始し、2023.01.01からはフォワード期間を2024.01.01まで実行します。

メイン期間2022.09.01〜2023.01.01

将来期間2023.01.01〜2024.01.01

図3:groupId_=1のパラメータで2022.09.01〜2024.01.01の期間における最終EAの結果

ご覧の通り、メイン期間(最適化期間と一致)ではEAは良好な結果を示していますが、フォワード期間では状況がまったく異なり、ドローダウンが大きく、エクイティカーブも目立った成長を示していません。もちろん、もっときれいな結果を期待したいところですが、この結果は予想外ではありません。最適化に使用した期間が非常に短く、銘柄数や時間足も少なかったため、既知の区間ではパラメータが特定の短期間に過剰適合していたことが原因です。EAは未知の区間で同じように機能できませんでした。

興味本位で、別の戦略グループでも同様のパターンが見られるか確認してみましょう。 今度はgroupId_=3に指定して最終EAを起動します。このグループは2022.11.01〜2023.03.01の期間で最適化されているため、テスターは2022.11.01からメイン期間を開始し、2023.03.01からフォワード期間を2024.01.01まで実行します。

メイン期間2022.11.01〜2023.03.01

将来期間2023.03.01〜2024.01.01

図4:groupId_=3のパラメータで2022.11.01〜2024.01.01の期間における最終EAの結果

結果は最初のグループと同様で、両グループとも5〜6月に大きなドローダウンが観測されました。この期間は戦略にとって不運だったのかと思うかもしれません。しかし、もしこの範囲で最適化されたグループを使用すると、同じグループのパラメータが問題なく選択され、チャートで滑らかで美しいカーブの成長が確認できます。 

次に、2023.01.01からgroupId_=0useAutoUpdate=falseで最終EAを起動すると、フォワード期間で最初のグループを読み込んだ場合と同じ結果になります。この場合、最初のグループがすでに存在するためです。しかし、自動更新が無効なため、後に出現するグループに置き換わることはありません。

最後に、自動更新を有効にして2023.01.01〜2024.01.01の期間で最終EAを起動します。groupId_=0useAutoUpdate=trueを指定します。

図5:groupId_=0、useAutoUpdate=trueのパラメータで2023.01.01〜2024.01.01の期間における最終EAの結果

取引結果自体は本記事の主題ではありません。自動最適化の時間短縮のため、最適化期間が非常に短く(わずか4か月)設定されているためです。今回の目的は、使用する戦略グループの自動更新機能が正しく動作することを示すことにあります。ログの記録や、各月の初めにポジションが自動でクローズされる挙動から判断すると、この機能は意図した通りに動作していることが確認されます。 

SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualReceiver::Get | OK, Strategy orders: 3 from 144 total
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 2.44, total strategies = 1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 48.00, total groups = 48
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualStrategyGroup::CVirtualStrategyGroup | Scale = 1.00, total groups = 1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualRiskManager::UpdateBaseLevels | DAILY UPDATE: Balance = 0.00 | Equity = 0.00 | Level = 0.00 | depoPart = 0.10 = 0.10 * 1.00 * 1.00
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualAdvisor::Load | LAST SAVE at 2023.01.31 20:32:00
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:00   CVirtualAdvisor::Load | UPDATE Group ID: 1 -> 2
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:00:59   CSymbolNewBarEvent::IsNewBar | Register new event handler for GBPUSD PERIOD_D1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:01:00   CSymbolNewBarEvent::IsNewBar | Register new event handler for EURUSD PERIOD_D1
SimpleVolumes (GBPUSD,H1)       2023.02.01 00:01:00   CSymbolNewBarEvent::IsNewBar | Register new event handler for EURGBP PERIOD_D1


結論

いくつかの結果をまとめてみましょう。ついに、再利用可能なすべてのコードをIncludeフォルダ内にAdvisorライブラリとしてまとめることができました。これにより、異なる取引戦略で動作するプロジェクトにも簡単に接続できるようになります。今後ライブラリを更新すれば、使用されているすべてのプロジェクトに自動的に反映されます。

自動最適化プロジェクトの作成と起動は、ますます容易になってきました。また、最適化結果を最終EAに反映させる仕組みも簡略化されました。最適化の第3ステージの設定で希望するEAデータベース名を指定するだけで、最終EAが取得できる場所に結果が保存されます。

ただし、まだ注意すべき点はいくつか残っています。その1つは、新しいタイプの取引戦略を追加するアルゴリズムを開発し、異なる種類の戦略を含むグループを最終EAに組み込む方法です。これは次回以降で詳しく取り上げます。次回その点について詳しく触れます。

ご精読ありがとうございました。またすぐにお会いしましょう。


重要な注意事項

この記事および連載のこれまでのすべての記事で提示された結果は、過去のテストデータのみに基づいており、将来の利益を保証するものではありません。このプロジェクトでの作業は研究的な性質のものであり、公開された結果はすべて、自己責任で使用されるべきです。


アーカイブ内容

#
 名前
バージョン  詳細  最近の変更
  MQL5/Experts/Article.16913   プロジェクト作業フォルダ  
1 CreateProject.mq5 1.01
ステージ、ジョブ、最適化タスクを含むプロジェクトを作成するためのEAスクリプト
第23回
2 HistoryReceiverExpert.mq5 1.01
リスクマネージャーとの取引履歴を再生するためのEA
第23回
3 Optimization.mq5
1.00 プロジェクト自動最適化用EA  第23回
4 SimpleVolumesExpert.mq5
1.22 複数のモデル戦略グループを並列操作するための最終EA(パラメータは組み込みのグループライブラリから取得)
第23回
5 Stage1.mq5 1.22  取引戦略単一インスタンス最適化EA(第1ステージ)
第23回
6 Stage2.mq5
1.00 取引戦略単一インスタンス最適化EA(第2ステージ)
第23回
Stage3.mq5
1.00 生成された標準化された戦略グループを、指定された名前のEAデータベースに保存するEA
第23回
  MQL5/Include/antekov/Advisor/Base
  他のプロジェクトクラスが継承する基本クラス  
8 Advisor.mqh 1.04 EA基本クラス 第10回
9 Factorable.mqh
1.05
文字列から作成されたオブジェクトの基本クラス
第22回
10 Interface.mqh 1.01
さまざまなオブジェクトを視覚化するための基本クラス
第4回
11 Receiver.mqh
1.04  オープンボリュームを市場ポジションに変換するための基本クラス
第12回
12 Strategy.mqh
1.04
取引戦略基本クラス
第10回
  MQL5/Include/antekov/Advisor/Database
  プロジェクトEAで使用されるすべての種類のデータベースを扱うファイル  
13 Database.mqh 1.10 データベースを扱うクラス 第22回
14 db.adv.schema.sql 1.00
最終EAのデータベース構造 第22回
15 db.cut.schema.sql
1.00 簡略化された最適化データベースの構造
第22回
16 db.opt.schema.sql
1.05  最適化データベース構造
第22回
17 Storage.mqh   1.01
EAデータベース内の最終EAのキー値ストレージを扱うクラス
第23回
  MQL5/Include/antekov/Advisor/Experts
  異なるタイプのEAで使用される共通部分のファイル
 
18 Expert.mqh  1.22 最終EAのライブラリファイル(グループパラメータはEAデータベースから取得)
第23回
19 Optimization.mqh  1.04 最適化タスクの起動を管理するEAのライブラリファイル
第23回
20 Stage1.mqh
1.19 単一インスタンス取引戦略最適化EAのライブラリファイル(第1ステージ)
第23回
21 Stage2.mqh 1.04 取引戦略インスタンスのグループを最適化するEAのライブラリファイル(第2ステージ)   第23回
22 Stage3.mqh
1.04 生成された標準化された戦略グループを、指定された名前のEAデータベースに保存するEAのライブラリファイル 第23回
  MQL5/Include/antekov/Advisor/Optimization
  自動最適化を担当するクラス  
23 Optimizer.mqh
1.03  プロジェクト自動最適化マネージャーのクラス
第22回
24 OptimizerTask.mqh
1.03
最適化タスククラス
第22回
  MQL5/Include/antekov/Advisor/Strategies    プロジェクトの動作を示すために使用される取引戦略の例
 
25 HistoryStrategy.mqh 
1.00 取引履歴を再生するための取引戦略のクラス
第16回
26 SimpleVolumesStrategy.mqh
1.11
ティックボリュームを使用した取引戦略のクラス
第22回
  MQL5/Include/antekov/Advisor/Utils
  補助ユーティリティ、コード削減用マクロ
 
27 ExpertHistory.mqh 1.00 取引履歴をファイルにエクスポートするクラス 第16回
28 Macros.mqh 1.05 配列操作に便利なマクロ 第22回
29 NewBarEvent.mqh 1.00  特定の銘柄の新しいバーを定義するクラス  第8回
30 SymbolsMonitor.mqh  1.00 取引商品(銘柄)に関する情報を取得するためのクラス 第21回
  MQL5/Include/antekov/Advisor/Virtual
  仮想の取引注文やポジションのシステムを用いた各種オブジェクト作成用クラス
 
31 Money.mqh 1.01  基本的なお金の管理クラス
第12回
32 TesterHandler.mqh  1.07 最適化イベント処理クラス  第23回
33 VirtualAdvisor.mqh  1.10  仮想ポジション(注文)を扱うEAのクラス 第23回
34 VirtualChartOrder.mqh  1.01  グラフィカル仮想ポジションクラス 第18回
35 VirtualFactory.mqh 1.04  オブジェクトファクトリクラス  第16回
36 VirtualHistoryAdvisor.mqh 1.00  トレード履歴再生EAクラス  第16回
37 VirtualInterface.mqh  1.00  EAGUIクラス  第4回
38 VirtualOrder.mqh 1.09  仮想注文とポジションのクラス  第22回
39 VirtualReceiver.mqh 1.04 オープンボリュームを市場ポジションに変換するクラス(レシーバー)  第23回
40 VirtualRiskManager.mqh  1.04 リスクマネジメントクラス(リスクマネージャー)  第23回
41 VirtualStrategy.mqh 1.09  仮想ポジションを使った取引戦略のクラス  第23回
42 VirtualStrategyGroup.mqh  1.02  取引戦略グループのクラス 第23回
43 VirtualSymbolReceiver.mqh  1.00 銘柄レシーバークラス  第3回
  MQL5/Common/Files   共有ターミナルフォルダ   
44 SimpleVolumes-27183.test.db.sqlite 戦略グループが追加されたEAデータベース  

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/16913

添付されたファイル |
MQL5.zip (421.44 KB)
取引における資金管理とデータベースを用いた個人向け会計プログラム 取引における資金管理とデータベースを用いた個人向け会計プログラム
トレーダーはどのように資金を管理すればよいのでしょうか。また、トレーダーや投資家はどのようにして支出、収入、資産、負債を把握すればよいのでしょうか。本記事では、単なる会計ソフトではなく、金融市場という荒波の中で意思決定を支える実践的なツールを紹介します。
血液型遺伝最適化(BIO) 血液型遺伝最適化(BIO)
人間の血液型の遺伝システムに着想を得た、新しい集団最適化アルゴリズム「血液型遺伝最適化(BIO)」を紹介します。このアルゴリズムでは、各解がそれぞれ固有の「血液型」を持ち、その血液型が進化の方法を決定します。自然界において子の血液型が特定の遺伝ルールに従って受け継がれるように、BIOでは新しい解が継承と突然変異の仕組みを通じて特性を獲得します。
純粋なMQL5で実装した通貨ペア強度インジケーター 純粋なMQL5で実装した通貨ペア強度インジケーター
MetaTrader 5向けの通貨強度分析用のプロフェッショナルなインジケーターを開発します。このステップバイステップガイドでは、強力な取引ツールを作成する方法を解説します。視覚的なダッシュボードを搭載し、複数の時間足(H1、H4、D1)で通貨ペアの強さを計算し、動的なデータ更新を実装し、ユーザーフレンドリーなインターフェースを作成することができます。
市場シミュレーション(第9回):ソケット(III) 市場シミュレーション(第9回):ソケット(III)
本日の記事は前回の記事の続編です。今回はエキスパートアドバイザー(EA)の実装を取り上げ、特にサーバー側コードがどのように実行されるかに焦点を当てます。前回の記事で示したコードだけでは、すべてを期待どおりに動作させるには不十分であるため、もう少し深く掘り下げる必要があります。そのため、これから起こることをよりよく理解するには、両方の記事を読む必要があります。