連続ウォークフォワード最適化(パート3):ロボットをオートオプティマイザに適応させる

7 4月 2020, 08:37
Andrey Azatskiy
0
503

イントロダクション

連続的なウォークフォワード最適化の自動オプティマイザの作成に関するシリーズの3番目の記事です。 前の 2 つの記事は、次のリンクから参照できます。

  1. 連続ウォークフォワード最適化(パート1):最適化レポートの操作
  2. 連続ウォークフォワード最適化(パート2):ロボットの最適化レポート作成機構

このシリーズの最初の記事は、自動オプティマイザが動作するために必要なトレードレポートファイルを動作させ、形成するためのメカニズムの作成を紹介しました。 2番目の記事では、トレードヒストリーのダウンロードを実装し、ダウンロードしたデータに基づいてトレードレポートを作成する主要なオブジェクトを紹介しました。 現在の記事は、前の 2 つの部分の間のブリッジとして機能します。最初の記事で検討されている.dll との相互作用のメカニズムと、2 番目の記事で説明したレポートダウンロード用のオブジェクトについて説明します。

DLLからインポートし、トレードヒストリーを持つXMLファイルを形成するクラスのラッパ作成のプロセスを分析します。 このラッパとデータのやり取りするメソッドも検討します。 この記事には、詳細なトレードヒストリーをダウンロードして、さらに分析を行う2つの機能の説明も含まれています。 結論として、オートオプティマイザで動作できるすぐに使用できるテンプレートを紹介します。 また、自動オプティマイザとデータのやり取りするために既存のアルゴリズムを修正する方法を示す、デフォルトのエキスパートセットの標準アルゴリズムの例を示します。 

蓄積されたトレードヒストリーをダウンロード

時には、さらなるヒストリー分析やその他の目的に、トレードヒストリーをファイルにダウンロードする必要があります。 残念ながら、このようなインタフェースはまだターミナルで利用できませんが、このタスクは前の記事で説明したクラスを使用して実装することができます。 記述されたクラスのファイルが配置されているディレクトリには、一般化された詳細データをダウンロードする"ShortReport.mqh"と"DealsHistory.mqh"という2つのファイルがあります。

まず、ShortReport.mqhファイルから始めましょう。 このファイルには関数とマクロが含まれており、その主なものは SaveReportToFile 関数です。 まず、データをファイルに書き込む'write'関数について考えてみましょう。 

//+------------------------------------------------------------------+
//| File writer                                                      |
//+------------------------------------------------------------------+
void writer(string fileName,string headder,string row)
  {
   bool isFile=FileIsExist(fileName,FILE_COMMON); // Flag of whether the file exists
   int file_handle=FileOpen(fileName,FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_SHARE_WRITE|FILE_SHARE_READ); // Open file
   if(file_handle) // If the file has opened
     {
      FileSeek(file_handle,0,SEEK_END); // Move cursor to the file end
      if(!isFile) // If it is a newly created file, write a header
         FileWrite(file_handle,headder);
      FileWrite(file_handle,row); // Write message
      FileClose(file_handle); // Close the file
     }
  }

データは、ファイルサンドボックスTerminal/Common/Filesに書き込まれます。 関数のアイデアは、行を追加してファイルにデータを書き込むことです、 ファイルを開き、そのハンドルを取得し、ファイルエンドに移動する理由です。 ファイルが作成されたばかりの場合は、渡されたヘッダを書き込みます

マクロに関しては、ファイルにロボットパラメータを追加するためのものだけです。

#define WRITE_BOT_PARAM(fileName,param) writer(fileName,"",#param+";"+(string)param);

このマクロでは、マクロの利点を利用し、マクロ名とその値を含む文字列を形成します。 パラメータ変数が関数にインプットされます。 マクロ使用例に詳細を示します。 

メインメソッドSaveReportToFileは長いので、ここではコード部分詳細なトレードレポートを書くだけです。 このメソッドは、CDealHistoryGetter クラスのインスタンスを作成し、1 つの行が 1 つのトレードを示す累積トレードヒストリーを持つ配列を受け取ります。

DealDetales history[];
CDealHistoryGetter dealGetter(_comission_manager);
dealGetter.getDealsDetales(history,0,TimeCurrent());

次に、ヒストリーが空でないことを確認し、CReportCreator クラスインスタンスを作成し、主な係数を持つ構造体を受け取ります。

if(ArraySize(history)==0)
   return;

CReportCreator reportCreator(_comission_manager);
reportCreator.Create(history,0);

TotalResult totalResult;
reportCreator.GetTotalResult(totalResult);
PL_detales pl_detales;
reportCreator.GetPL_detales(pl_detales);

次に、'writer' 関数を使用して、ヒストリーデータをループに保存します。 サイクルの最後に、次の係数と値を持つフィールドが追加されます。

  • PL
  • トレード総数
  • 連勝
  • 連続ドローダウン
  • リカバリーファクター
  • プロフィットファクター
  • ペイオフ
  • plによるドローダウン

writer(fileName,"","==========================================================================================================");
writer(fileName,"","PL;"+DoubleToString(totalResult.total.PL)+";");
int total_trades=pl_detales.total.profit.orders+pl_detales.total.drawdown.orders;
writer(fileName,"","Total trdes;"+IntegerToString(total_trades));
writer(fileName,"","Consecutive wins;"+IntegerToString(pl_detales.total.profit.dealsInARow));
writer(fileName,"","Consecutive DD;"+IntegerToString(pl_detales.total.drawdown.dealsInARow));
writer(fileName,"","Recovery factor;"+DoubleToString(totalResult.total.recoveryFactor)+";");
writer(fileName,"","Profit factor;"+DoubleToString(totalResult.total.profitFactor)+";");
double payoff=MathAbs(totalResult.total.averageProfit/totalResult.total.averageDD);
writer(fileName,"","Payoff;"+DoubleToString(payoff)+";");
writer(fileName,"","Drawdown by pl;"+DoubleToString(totalResult.total.maxDrawdown.byPL)+";");

メソッドの操作はここで完了します。 さて、ヒストリーをダウンロードする簡単な方法を考えてみましょう。標準の配信パッケージ、Experts/Examples/Moving Average/Moving Average.mq5からEAにこの関数を追加してみましょう。 まず、ファイルを接続する必要があります。

#include <History manager/ShortReport.mqh>

次に、カスタムコミッションとスリッページを設定する変数をインプットに追加します。

input double custom_comission = 0; // Custom commission;
input int custom_shift = 0; // Custom shift;

コミッションとスリッページを条件付きでではなく、ディレクティブに設定したい場合は(前の記事のCDealHistoryGetterクラスの説明を参照してください)、ファイルを接続する前に、ONLY_CUSTOM_COMISSIONパラメータをそのとおりに決定する必要があります。

#define ONLY_CUSTOM_COMISSION
#include <History manager/ShortReport.mqh>

次に、CCCMクラスサンプルを作成し、OnInitメソッドでコミッションとスリッページをこのクラスインスタンスに追加し、コミッションを保存します。

CCCM _comission_manager_;

...

int OnInit(void)
  {
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);  

...

  }

次に、OnDeinit メソッドに次のコード行を追加します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string file_name = __FILE__+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);

      WRITE_BOT_PARAM(file_name,MaximumRisk);      // Maximum Risk in percentage
      WRITE_BOT_PARAM(file_name,DecreaseFactor);   // Decrease factor
      WRITE_BOT_PARAM(file_name,MovingPeriod);     // Moving Average period
      WRITE_BOT_PARAM(file_name,MovingShift);      // Moving Average shift
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift
     }
  }

このコードは、ロボットインスタンスの削除後にチェックを実行します: テスターで実行されているかどうかの条件がチェックされます。 ロボットがテスターで実行されている場合、ロボットトレードヒストリーを"Compiled_file_name Report.csv"というファイルに保存する関数が呼び出されます。 ファイルに書き込まれるすべてのデータの後に、このファイルのインプットパラメータに 6 行を追加します。 ストラテジーテスターでEAをテストモードで起動するたびに、EAが実行したトレードの説明を含むファイルが届きます。 このファイルは、新しいテストを開始するたびに上書きされます。 ファイルは、ファイルサンドボックスのCommon/Filesディレクトリに保存されます。

トレード別に分割されたトレードヒストリーのダウンロード

ここでは、詳細なトレードレポート、つまりすべてのトレードがポジションにグループ化されたレポートをダウンロードする方法を見てみましょう。 このために、既に ShortReport.mqh ファイル接続が含まれている DealsHistory.mqh ファイルを使用します。 唯一の DealsHistory.mqh ファイルを接続することで、両方のメソッドを同時に使用できることを意味します。

ファイルには 2 つの関数があります。 最初の関数は普通の関数で、合計をします。

void AddRow(string item, string &str)
  {
   str += (item + ";");
  }

2 番目の関数は、先ほどの 'writer' 関数を使用してファイルにデータを書き込みます。 その完全な実装は以下に示されています。

void WriteDetalesReport(string fileName,CCCM *_comission_manager)
  {

   if(FileIsExist(fileName,FILE_COMMON))
     {
      FileDelete(fileName,FILE_COMMON);
     }

   CDealHistoryGetter dealGetter(_comission_manager);

   DealKeeper deals[];
   dealGetter.getHistory(deals,0,TimeCurrent());

   int total= ArraySize(deals);

   string headder = "Asset;From;To;Deal DT (Unix seconds); Deal DT (Unix miliseconds);"+
                    "ENUM_DEAL_TYPE;ENUM_DEAL_ENTRY;ENUM_DEAL_REASON;Volume;Price;Comission;"+
                    "Profit;Symbol;Comment";

   for(int i=0; i<total; i++)
     {
      DealKeeper selected = deals[i];
      string asset = selected.symbol;
      datetime from = selected.DT_min;
      datetime to = selected.DT_max;

      for(int j=0; j<ArraySize(selected.deals); j++)
        {
         string row;
         AddRow(asset,row);
         AddRow((string)from,row);
         AddRow((string)to,row);

         AddRow((string)selected.deals[j].DT,row);
         AddRow((string)selected.deals[j].DT_msc,row);
         AddRow(EnumToString(selected.deals[j].type),row);
         AddRow(EnumToString(selected.deals[j].entry),row);
         AddRow(EnumToString(selected.deals[j].reason),row);
         AddRow((string)selected.deals[j].volume,row);
         AddRow((string)selected.deals[j].price,row);
         AddRow((string)selected.deals[j].comission,row);
         AddRow((string)selected.deals[j].profit,row);
         AddRow(selected.deals[j].symbol,row);
         AddRow(selected.deals[j].comment,row);

         writer(fileName,headder,row);

        }

      writer(fileName,headder,"");
     }


  }

トレードデータを受信し、ヘッダを作成した後、詳細なトレードレポートに進みます。 このために、ネストされたループを実装します:メインループはポジションで動作し、ネストされたループはポジション内のトレードをループします。 新しいポジション(すなわちポジションを構成する一連のトレード)を書いた後、ポジションはスペースを使用して区切られます。 これより、結果として得られるファイルを効率的に読み取ります。 この関数をロボットに追加するために劇的な変更を加える必要はありませんが、OnDeinit で呼び出しを実行するだけで済みます。

void OnDeinit(const int reason)
  {
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string file_name = __FILE__+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);

      WRITE_BOT_PARAM(file_name,MaximumRisk);      // Maximum Risk in percentage
      WRITE_BOT_PARAM(file_name,DecreaseFactor);   // Descrease factor
      WRITE_BOT_PARAM(file_name,MovingPeriod);     // Moving Average period
      WRITE_BOT_PARAM(file_name,MovingShift);      // Moving Average shift
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(__FILE__+" Deals Report.csv", &_comission_manager_);
     }
  }

データのダウンロード方法を詳しく説明するために、レポートをダウンロードするためのメソッドが追加された空のEAテンプレートを次に示します。

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define ONLY_CUSTOM_COMISSION
#include <History manager/DealsHistory.mqh>

input double custom_comission   = 0;       // Custom commission;
input int    custom_shift       = 0;       // Custom shift;

CCCM _comission_manager_;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string arr[];
      StringSplit(__FILE__,'.',arr);
      string file_name = arr[0]+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(arr[0]+" Deals Report.csv", &_comission_manager_);
     }
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

上記のテンプレートに任意のロジックを追加することで、ストラテジーテスターでEAテスト完了後にトレードレポートの生成を有効にすることができます。

蓄積されたトレーディングヒストリーを作成するDLLのラッパ

このシリーズの最初の記事は、最適化レポートを操作するための C# 言語での.dll の作成に専念しました。 連続的なウォークフォワード最適化の最も便利な形式はXMLであるため、結果のレポートを読み取り、書き込み、並べ替えできるDLLを作成しました。 EAでは、データの書き込み関数のみが必要です。 ただし、純粋な関数を使用した操作はオブジェクトよりも便利ではないため、データダウンロード関数のラッパクラスが作成されています。 このオブジェクトは、XmlHistoryWriter.mqhファイル内にあり、СXmlHistoryWriterを呼び出します。 オブジェクトに加えて、EAパラメータの構造を定義します。 この構造体は、EAパラメータのリストをオブジェクトに渡すときに使用します。 実装の詳細をすべて考えてみましょう。 

最適化レポートの作成を有効にするには、ReportCreator.mqh ファイルを接続します。 最初の記事で説明したDLLのクラスの静的メソッドを使用するには、インポートしましょう(ライブラリはMQL5/Librariesディレクトリの下ですでに利用可能でなければなりません)。

#include "ReportCreator.mqh"
#import "ReportManager.dll"

必要なリンクを追加した後、パラメータコレクションにロボットパラメータを追加し、ターゲットクラスに渡すメソッドを確認します。 

struct BotParams
  {
   string            name,value;
  };

#define ADD_TO_ARR(arr, value) \
{\
   int s = ArraySize(arr);\
   ArrayResize(arr,s+1,s+1);\
   arr[s] = value;\
}

#define APPEND_BOT_PARAM(Var,BotParamArr) \
{\
   BotParams param;\
   param.name = #Var;\
   param.value = (string)Var;\
   \
   ADD_TO_ARR(BotParamArr,param);\
}

オブジェクトのコレクションを操作するので、動的配列を扱う方が楽になります。 ADD_TO_ARRマクロは、動的配列に要素を追加するために作成されました。 マクロはコレクションのサイズを変更し、渡された項目を追加します。 マクロは、ユニバーサル ソリューションを提供します。 したがって、動的配列に任意の型の値を素早く追加できるようになりました。

次のマクロは、パラメータを直接使用します。 このマクロは、BotParams 構造体インスタンスを作成し、配列にパラメータの説明を追加する配列と、このパラメータを格納する変数だけをインプットして、配列に追加します。 マクロは、変数名に基づいたパラメータに適切な名前を割り当て、文字列形式に変換されたパラメータ値を割り当てます。

文字列形式により、*.set ファイルと *.xml ファイルに保存されるデータの設定が適切に対応します。 前の記事で既に説明したように、set ファイルはEAインプットパラメータをキー値形式で格納し、コード内の変数名がキーとして受け入れられ、インプットパラメータに割り当てられた値が値として受け入れられます。 すべての列挙型 (num) は、EnumToString() 関数の結果ではなく、int 型として指定する必要があります。 説明されたマクロは、すべてのパラメータを必要な文字列型に変換しますが、すべての列挙型は最初に int に変換されてから、必要な文字列形式に変換されます。

また、ロボットパラメータの配列を別の配列にコピーできる関数も宣言しました。

void CopyBotParams(BotParams &dest[], const BotParams &src[])
  {
   int total = ArraySize(src);
   for(int i=0; i<total; i++)
     {
      ADD_TO_ARR(dest,src[i]);
     }
  }

標準の ArrayCopy 関数は構造体の配列では動作しないため、これが必要です。 

ラッパ クラスは次のように宣言されます。

class CXmlHistoryWriter
  {
private:
   const string      _path_to_file,_mutex_name;
   CReportCreator    _report_manager;

   string            get_path_to_expert();//

   void              append_bot_params(const BotParams  &params[]);//
   void              append_main_coef(PL_detales &pl_detales,
                                      TotalResult &totalResult);//
   double            get_average_coef(CoefChartType type);
   void              insert_day(PLDrawdown &day,ENUM_DAY_OF_WEEK day);//
   void              append_days_pl();//

public:
                     CXmlHistoryWriter(string file_name,string mutex_name,
                     CCCM *_comission_manager);//
                     CXmlHistoryWriter(string mutex_name,CCCM *_comission_manager);
                    ~CXmlHistoryWriter(void) {_report_manager.Clear();} //

   void              Write(const BotParams &params[],datetime start_test,datetime end_test);//
  };

ファイルへの書き込み用に、2 つの定数文字列フィールドが宣言されています。

  • _path_to_file
  • _mutex_name

最初のフィールドには、データの書き込み先となるファイルへのパスが含まれます。 2 番目のフィールドには、使用するミューテックスの名前が含まれます。 名前付きミューテックスの実装は C#.dll で提供されます。 最適化プロセスは、異なるスレッド、異なるコア、および異なるプロセス(ロボットの1回の起動= 1つのプロセス)で実装されるため、このミューテックスが必要です。 したがって、2 つの最適化が完了し、2 つ以上のプロセスが同時に同じファイルに結果を書き込もうとする状況が発生する可能性があります。 この状況を解消するために、オペレーティング システムのコア、つまり名前付きミューテックスに基づく同期オブジェクトを使用します。 

CReportCreator クラスインスタンスは、他の関数がこのオブジェクトにアクセスするため、フィールドとして必要とされます。 では、各メソッドの実装を見てみましょう。

まず、クラスコンストラクタを始めましょう。

CXmlHistoryWriter::CXmlHistoryWriter(string file_name,
                                     string mutex_name,
                                     CCCM *_comission_manager) : _mutex_name(mutex_name),
   _path_to_file(TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\"+file_name),
   _report_manager(_comission_manager)
  {
  }
CXmlHistoryWriter::CXmlHistoryWriter(string mutex_name,
                                     CCCM *_comission_manager) : _mutex_name(mutex_name),
   _path_to_file(TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\"+MQLInfoString(MQL_PROGRAM_NAME)+"_"+"Report.xml"),
   _report_manager(_comission_manager)
  {
  }

このクラスには 2 つのコンストラクタがあります。 最適化レポートが格納されているファイルの名前を設定する 2 番目のコンストラクタに注意してください。 次の記事で検討される自動オプティマイザでは、カスタム最適化マネージャを設定することが可能になります。 デフォルトのハードコードされたマネージャは、ロボットによって生成されたレポートファイルの命名に関する合意を既に実装します。 したがって、2 番目のコンストラクタはこのアグリーメントを設定します。 従って、ファイル名はEA名で始まり、続いてアンダースコアと接尾辞 "_Report.xml" が続く必要があります。 DLL は PC 上のどこにでもレポート ファイルを書き込むことができますが、ファイルがターミナル操作に属する情報を保持するために、MetaTrader5 サンドボックスのCommon ディレクトリに常に保存します。 

EAへのパスを受け取るメソッド:

string CXmlHistoryWriter::get_path_to_expert(void)
  {
   string arr[];
   StringSplit(MQLInfoString(MQL_PROGRAM_PATH),'\\',arr);
   string relative_dir=NULL;

   int total= ArraySize(arr);
   bool save= false;
   for(int i=0; i<total; i++)
     {
      if(save)
        {
         if(relative_dir== NULL)
            relative_dir=arr[i];
         else
            relative_dir+="\\"+arr[i];
        }

      if(StringCompare("Experts",arr[i])==0)
         save=true;
     }

   return relative_dir;
  }

EAパスは、選択したEAの自動起動に必要です。 ここには、そのパスは、ターミナルの起動時に渡される ini ファイルで指定する必要があります。 パスは、現在のEAへのパスを受け取る関数によって取得される完全なパスではなく、Experts フォルダに対して相対的に指定する必要があります。 したがって、まず、取得したパスをコンポーネントに分割する必要があります。 次に、ループ内で、最初のディレクトリから始まる Experts ディレクトリを検索します。 検出された後、次のディレクトリ(またはEAファイルが目的のディレクトリのルートに直接配置されている場合)から始まるロボットへのパスを形成します。

append_bot_paramsメソッド:

このメソッドは、同じ名前のインポートされたメソッドのラッパです。 その実装は次のとおりです。

void CXmlHistoryWriter::append_bot_params(const BotParams &params[])
  {

   int total= ArraySize(params);
   for(int i=0; i<total; i++)
     {
      ReportWriter::AppendBotParam(params[i].name,params[i].value);
     }
  }

前述のEAパラメータの配列は、このメソッドにインプットされます。 次に、ループ内で、EAパラメータごとに.dll からインポートされたメソッドを呼び出します。

append_main_coefメソッドの実装は簡単なので、ここでは考慮しません。 インプットパラメータとして CReportCreator クラスの構造体を受け入れます。

get_average_coefメソッドは、渡された係数チャートに基づく単純MA法で係数の平均値を計算することを目的とします。 平均利益率と平均リカバリーファクターを計算するために使用します。  

insert_dayメソッドは、インポートされた ReportWriter::AppendDay メソッドの呼び出されるラッパです。 append_days_plメソッドは既に前述のラッパを使用します。

ラッパ メソッドの中には、メイン メソッドとして機能する 1 つのパブリック メソッドがあります。

void CXmlHistoryWriter::Write(const BotParams &params[],datetime start_test,datetime end_test)
  {
   if(!_report_manager.Create())
     {
      Print("##################################");
      Print("Can`t create report:");
      Print("###################################");
      return;
     }
   TotalResult totalResult;
   _report_manager.GetTotalResult(totalResult);
   PL_detales pl_detales;
   _report_manager.GetPL_detales(pl_detales);

   append_bot_params(params);
   append_main_coef(pl_detales,totalResult);

   ReportWriter::AppendVaR(totalResult.total.VaR_absolute.VAR_90,
                           totalResult.total.VaR_absolute.VAR_95,
                           totalResult.total.VaR_absolute.VAR_99,
                           totalResult.total.VaR_absolute.Mx,
                           totalResult.total.VaR_absolute.Std);

   ReportWriter::AppendMaxPLDD(pl_detales.total.profit.totalResult,
                               pl_detales.total.drawdown.totalResult,
                               pl_detales.total.profit.orders,
                               pl_detales.total.drawdown.orders,
                               pl_detales.total.profit.dealsInARow,
                               pl_detales.total.drawdown.dealsInARow);
   append_days_pl();

   string error_msg=ReportWriter::MutexWriter(_mutex_name,get_path_to_expert(),AccountInfoString(ACCOUNT_CURRENCY),
                    _report_manager.GetBalance(),
                    (int)AccountInfoInteger(ACCOUNT_LEVERAGE),
                    _path_to_file,
                    _Symbol,(int)Period(),
                    start_test,
                    end_test);
   if(StringCompare(error_msg,"")!=0)
     {
      Print("##################################");
      Print("Error while creating (*.xml) report file:");
      Print("_________________________________________");
      Print(error_msg);
      Print("###################################");
     }
  }

レポートの作成に失敗した場合は、ログに適切なインプットが追加されます。 レポートが正常に作成された場合は、目的の係数を受け取り、上記のメソッドを1つずつ呼び出します。 最初の記事によると、メソッドはリクエストされたパラメータを C# クラスに追加します。 その後、ファイルにデータを書き込むメソッドが呼び出されます。 書き込みが失敗した場合、error_msgはエラーのテキストを含み、テスターログに書き込まれます。 

結果のクラスは、トレーディングレポートを生成し、'Write'メソッドの呼び出し時にファイルにデータを書き込むことができます。 しかし、インプットパラメータと他の何も扱わないように、プロセスをさらに簡素化したいと思います。 このために、次のクラスが作成されました。

CAutoUpLoader クラスは、テストの完了時にトレードレポートを自動的に生成します。 AutoLoader.mqh ファイルにあります。 クラス操作の場合は、前述のクラスへのリンクを追加し、XML 形式でレポートを生成します。

#include <History manager/XmlHistoryWriter.mqh>

クラスシグネチャはシンプルです。

class CAutoUploader
  {
private:

   datetime          From,Till;
   CCCM              *comission_manager;
   BotParams         params[];
   string            mutexName;

public:
                     CAutoUploader(CCCM *comission_manager, string mutexName, BotParams &params[]);
   virtual          ~CAutoUploader(void);

   virtual void      OnTick();

  };

このクラスには、仮想デストラクターと同様に、オーバーロード OnTick メソッドがあります。 これより、クラスは、集計と継承の使用の両方を適用できます。 これが私が言いたいことです。 このクラスの目的は、各ティックでのテスト完了時間を上書きすることと、テストの開始時刻を記録することです。 上記のオブジェクトを使用する場合は、 2 つのパラメータが必要です。 アプリケーションには、ロボットクラスのどこかでこのオブジェクトをインスタンス化するか(ロボットがOOPを使用して開発されている場合)、またはグローバルスコープで、このソリューションをC型プログラミングに使用できます。

その後、この関数ではクラスインスタンスの OnTick() メソッドが呼び出されます。 クラスオブジェクトを破棄した後、トレーディングレポートはデストラクタでアンロードされます。 クラスを適用する 2 番目の方法は、EAクラスを継承することです。 仮想デストラクタと OnTick() オーバーロード メソッドは、この目的に作成されます。 2番目の方法を適用した結果、EAと直接連携します。 このクラスの実装は、CXmlHistoryWriter クラスに操作をデリゲートするので、簡単です。

void CAutoUploader::OnTick(void)
  {
   if(MQLInfoInteger(MQL_OPTIMIZATION)==1 ||
      MQLInfoInteger(MQL_TESTER)==1)
     {
      if(From == 0)
         From = iTime(_Symbol,PERIOD_M1,0);
      Till=iTime(_Symbol,PERIOD_M1,0);
     }
  }
CAutoUploader::CAutoUploader(CCCM *_comission_manager,string _mutexName,BotParams &_params[]) : comission_manager(_comission_manager),
   mutexName(_mutexName)
  {
   CopyBotParams(params,_params);
  }
CAutoUploader::~CAutoUploader(void)
  {
   if(MQLInfoInteger(MQL_OPTIMIZATION)==1 ||
      MQLInfoInteger(MQL_TESTER)==1)
     {
      CXmlHistoryWriter historyWriter(mutexName,
                                      comission_manager);

      historyWriter.Write(params,From,Till);
     }
  }

EAテンプレートに説明された関数を追加してみましょう。

//+------------------------------------------------------------------+
//|                                                         Test.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define ONLY_CUSTOM_COMISSION
#include <History manager/DealsHistory.mqh>
#include <History manager/AutoLoader.mqh>

class CRobot;

input double custom_comission   = 0;       // Custom commission;
input int    custom_shift       = 0;       // Custom shift;

CCCM _comission_manager_;
CRobot *bot;
const string my_mutex = "My Mutex Name for this expert";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   _comission_manager_.add(_Symbol,custom_comission,custom_shift);

   BotParams params[];

   APPEND_BOT_PARAM(custom_comission,params);
   APPEND_BOT_PARAM(custom_shift,params);

   bot = new CRobot(&_comission_manager_,my_mutex,params);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(MQLInfoInteger(MQL_TESTER)==1)
     {
      string arr[];
      StringSplit(__FILE__,'.',arr);
      string file_name = arr[0]+" Report.csv";
      SaveReportToFile(file_name,&_comission_manager_);
      WRITE_BOT_PARAM(file_name,custom_comission); // Custom commission;
      WRITE_BOT_PARAM(file_name,custom_shift);     // Custom shift;

      WriteDetalesReport(arr[0]+" Deals Report.csv", &_comission_manager_);
     }

   delete bot;
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   bot.OnTick();
  }
//+------------------------------------------------------------------+


//+------------------------------------------------------------------+
//| Main Robot class                                                 |
//+------------------------------------------------------------------+
class CRobot : CAutoUploader
  {
public:
                     CRobot(CCCM *_comission_manager, string _mutexName, BotParams &_params[]) : CAutoUploader(_comission_manager,_mutexName,_params)
     {}

   void              OnTick() override;
  };

//+------------------------------------------------------------------+
//| Robot logic triggering method                                    |
//+------------------------------------------------------------------+
void CRobot::OnTick(void)
  {
   CAutoUploader::OnTick();

   Print("This should be the robot logic start");
  }
//+------------------------------------------------------------------+

したがって、最初に行うことは、XMLへの自動レポートダウンロードのラッパクラスが格納されているファイルへの参照を追加することです。 ロボットクラスは、プロジェクトの最後に実装して記述する方が簡単であるため、あらかじめ決められています。 通常、MQL5プロジェクトとしてアルゴリズムを作成しますが、ロボットと関連するクラスのクラスはファイルに分かれているため、1ページのアプローチよりもはるかに便利です。 しかし、便宜上、すべてが1つのファイルに入れられました。
その後、クラスについて説明します。 この例では、1 つのオーバーロードされた OnTick メソッドを持つ空のクラスです。 したがって、2 番目の CAutoUploader アプリケーションの方法、つまり継承を使用します。 オーバーロードされた OnTick メソッドでは、日付の計算がストップしないように、基本クラスの OnTick メソッドを明示的に呼び出す必要があることに注意してください。 オートオプティマイザの操作全体に不可欠です。

次のステップでは、グローバル スコープからではなく OnInit メソッドから取り込む方が便利なので、robot を使用してクラスへのポインタを作成します。 また、ミューテックス名を格納する変数が作成されます

ロボットは OnInit メソッドでインスタンス化され、OnDeinit で削除されます。 新しいティック到着コールバックをロボットに確実に渡すために、ロボットポインタでオーバーロードされたOnTick()メソッドを呼び出します。 完了したら、CRobotクラスでロボットを書きます。

集計またはグローバル スコープで CAutoUpLoader インスタンスを作成することによってレポートをダウンロードする場合の亜種は似ています。 ご不明な点がございましたら、お気軽にお問い合わせください。 

したがって、このロボットテンプレートを使用するか、既存のアルゴリズムに適切な呼び出しを追加することで、次の記事で説明する自動オプティマイザと一緒に使用できます。

結論

最初の記事では、XMLレポートファイルによる操作のメカニズムとファイル構造の作成を分析しました。 レポートの作成は、第2の記事で検討されました。 レポート生成メカニズムは、ヒストリーダウンロードオブジェクトから始まり、レポートを生成するオブジェクトで終わるように検討しました。 レポート作成プロセスに関連するオブジェクトを調査する際、計算部分を詳細に分析しました。 この記事には、主な係数の公式と、可能な計算の問題の説明も含まれています。

この記事の紹介で説明したように、このパートで説明するオブジェクトは、データダウンロードメカニズムとレポート生成メカニズムの間の橋渡し役となります。 トレーディングレポートファイルを保存する機能に加えて、XMLレポートのアンロードに参加するクラスの説明と、関数を自動的に使用できるロボットテンプレートの説明も含まれています。 また、作成した関数を既存のアルゴリズムに追加する方法についても説明しました。 これは、自動オプティマイザのユーザーが古いアルゴリズムと新しいアルゴリズムの両方を最適化できることを意味します。   

添付されたアーカイブには 2 つのフォルダがあります。 両方を MQL/Includディレクトリに解凍します。 ReportManager.dllライブラリは、MQL5/Librariesに追加する必要があります。 前のarticleからダウンロードできます。

添付ファイルには、次のファイルがあります。

  1. CustomGeneric
    • GenericSorter.mqh
    • ICustomComparer.mqh
  2. History manager
    • AutoLoader.mqh
    • CustomComissionManager.mqh
    • DealHistoryGetter.mqh
    • DealsHistory.mqh
    • ReportCreator.mqh
    • ShortReport.mqh
    • XmlHistoryWriter

MetaQuotes Software Corp.によりロシア語から翻訳された
元の記事: https://www.mql5.com/ru/articles/7490

添付されたファイル |
Include.zip (29.82 KB)
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第24部): 未決取引リクエストの使用 - リクエストオブジェクトクラス MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第24部): 未決取引リクエストの使用 - リクエストオブジェクトクラス

前の記事では、保留中取引リクエストの概念を確認しました。保留中リクエストは、実際には、特定の条件によって実行される一般的な取引注文です。本稿では、保留中リクエストオブジェクトの完全なクラス(基本リクエストオブジェクトとその子孫)を作成します。

パーセプトロンニューラルネットワーク パーセプトロンニューラルネットワーク

人工知能は、多くの場合、幻想的で複雑で理解できない何かに関連付けられます。 同時に、人工知能は日常生活の中でますます言及されています。 ニューラルネットワークの使用に関する成果に関するニュースは、多くのさまざまなメディアで取り上げられています。 この記事の目的は、誰でもニューラルネットワークを作成し、トレードでAIの成果をあげることを示すためにあります。

MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第25部): 未決取引リクエスト - リクエストオブジェクトの管理 MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第25部): 未決取引リクエスト - リクエストオブジェクトの管理

前の記事では、ライブラリオブジェクトの一般的な概念に対応する保留中リクエストオブジェクトのクラスを作成しました。今回は、保留中リクエストオブジェクトの管理を許可するクラスについてです。

相場パターンを見つけるための計量的アプローチ:自己相関、ヒートマップ、散布図 相場パターンを見つけるための計量的アプローチ:自己相関、ヒートマップ、散布図

この記事では、季節的特徴の拡張である自己相関ヒートマップと散布図を紹介します。 この記事の目的は、"マーケットメモリ"が季節的な性質を持ち、任意のオーダーの増分の最大相関によって表現されることを示すものです。