请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3195052 新帖:257

连续前行优化 (第三部分): 将机器人适配为自动优化器

舞蹈家2发表于:4 月 1 日 17:40回复(1)

概述

本文是该系列的第三篇,该连续前行优化系列致力于创建自动优化器。 以下链接指向前两篇文章:

  1. 连续前行优化 (第一部分): 操控优化报告
  2. 连续前行优化 (第二部分): 创建优化报告机器人的机理

本系列的第一篇文章专门研讨一种操控和形成交易报告文件的创建机制,这是自动优化器运作时所必需的。 第二篇文章则研讨交易历史记录下载、和基于下载数据创建交易报告的关键对象实现。 本文充当前两部分之间的桥梁:它阐述的是第一篇文章中研究的 DLL,以及第二篇文章中论述的报告下载对象之间的交互机制。

我们将分析从 DLL 导入的包装类的创建过程,该类可依据交易历史记录形成 XML 文件。 我们还将研究一种与此包装器进行交互的方法。 本文还包含两个函数的论述,这些函数下载详细和常规交易历史记录,便于进一步分析。 在结论里,我将介绍一个可协同自动优化器一起操作的现成模板。 我还将展示标准算法示例,它来自默认智能系统集,该示例演示如何修改现有算法,从而与自动优化器进行交互。 

下载积累的交易历史

有时我们需要将交易历史记录加载至文件中,便于进一步的分析和其他目的。 不幸地是,在终端中尚未提供这样的接口,但可利用上一篇文章中研讨的类来实施此任务。 研讨类的文件所在的目录中还有另外两个文件 “ShortReport.mqh” 和 “DealsHistory.mqh”,这些文件可下载常规和详细数据。

我们从 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 类的实例,并接收含有累积交易历史记录的数组,其中每一行表示一笔成交。

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
  • Total trades
  • Consecutive wins
  • Consecutive Drawdowns
  • Recovery factor
  • Profit factor
  • Payoff
  • Drawdown by 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 里添加此功能。 首先,我们需要连接文件:

#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 执行的成交说明。 每当我们开始新的测试时,此文件将被覆盖。 该文件将存储在 Common/Files 目录下的文件沙箱中。

下载按成交划分的交易历史记录

现在我们查看如何下载详细的交易报告,即,将所有成交按仓位分组报告。 为此目的,我们将利用 DealsHistory.mqh 文件,该文件已经包含 ShortReport.mqh 文件连接。 这意味着仅需连接一个 DealsHistory.mqh 文件,我们就可同时使用两个方法。

该文件包含两个函数。 第一个是一个普通函数,它能漂亮地完成求和:

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

第二个函数利用先前研讨的 “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 可以读取、写入和排序结果报告。 在智能交易系统当中,我们仅需数据写入功能。 不过,由于利用纯函数进行的操作不如对象方便,故创建了下载数据的包装器类。 该对象位于 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 文件中的数据正确对应。 如之前文章中所述,配置文件以键-值格式存储 EA 输入参数,其中将代码中的变量名作为键,将分配给输入参数的值作为值。 必须将所有枚举(num)指定为 int 类型,而非 EnumToString() 函数的结果。 所描述的宏定义将所有参数转换为所需的字符串类型,而所有枚举都首先转换为 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);//
  };

声明了两个写入文件时要用到的字符串字段常量:

  • _path_to_file
  • _mutex_name

第一个字段包含即将写入数据的文件路径。 第二个字段包含所用互斥锁的名称。 C# DLL 中提供了已命名互斥锁的实现。 我们需要此互斥锁,因为优化过程是在不同的线程、不同的内核和不同的进程里实施(机器人启动一次 = 一个进程)。 所以,可能发生以下状况:两个优化已完成,且两个或多个进程同时尝试将结果写入同一文件,这是不可接受的。 为了消除这种情况,我们利用基于操作系统核心的同步对象,即已命名互斥锁。 

需要 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)
  {
  }

该类包含两个构造函数。 请注意第二个构造函数,该构造函数设置存储优化报告的文件名称。 在下一篇文章将要讨论的自动优化器中,能够设置自定义优化管理器。 默认硬编码管理器针对机器人生成的报告文件已实现了命名协议。 因此,第二个构造函数设置此协议。 据此,文件名必须以 EA 名称开头,后跟下划线和后缀 “_Report.xml”。 尽管 DLL 可以在 PC 上的任何位置写入报告文件,但是为了保留该文件是属于终端操作的信息,我们将始终将其存储在 MetaTrader 5 沙箱的 Common 目录之下。 

接收智能交易系统路径的方法:

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 路径。 为此,应在终端启动时传递的 ini 文件中指定其路径。 应指定相对于 Experts 文件夹的路径,而非完整路径,完整路径可由接收当前 EA 路径的函数获得。 因此,我们首先需要将获得的路径拆分为多个分量,其中的分隔符为斜线。 然后,在循环里从第一层目录开始搜索 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 方法旨在基于所传递的系数图表,按简单均值方法计算系数的平均值。 它们会用于计算平均利润率和平均恢复率。  

对于导入的 ReportWriter::AppendDay 方法,insert_day 方法是一个易于调用的包装器。 append_days_pl 方法已用到了早前提到的包装器。

在所有这些包装器方法中,有一个起主导的公开方法 — 它是 “Write” 方法,它会触发保存数据的整个机制。

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("###################################");
     }
  }

如果尝试创建报告失败,则会将相应的记录附加到日志中。 如果创建报告成功,我们将继续接收所需的系数,然后逐一调用上述方法。 根据第一篇文章,这些方法将所请求参数添加到 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 方法,以及虚拟的析构函数。 这可确保同时应用于聚合和继承。 这就是我的意思。 该类的目的是在每次即时报价时覆盖测试完成时间,并记住测试的开始时间。 这两个参数是使用前述对象所必需的。 有若干种方法可以应用它:我们既可在机器人类的某个地方(如果机器人是运用面向对象开发的)实例化该对象,也可在全局范围内实例化此对象–此解决方案可用于类 C 的编程。

之后,在该函数中调用类实例的 OnTick() 方法。 销毁类对象后,交易报告会在其析构函数中卸载。 应用该类的第二种方法是在 EA 里继承该类。 为此目的,从而创建了虚拟析构函数和 OnTick() 重载方法。 应用第二种方法的结果就是,我们将直接运用智能交易系统操作。 该类的实现很简单,这在于它将操作委托给 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 项目,而这比分页方式要方便得多,因为含有机器人的类和相关的类可切分到多个文件。 不过,出于便捷起见,所有内容都放在一个文件中。
然后是描述该类。 在此示例中,它是一个含有一个重载 OnTick 方法的空白类。 因此,运用第二种 CAutoUploader 应用方式,即继承。 请注意,在重载的 OnTick 方法中,有必要显式调用基类的 OnTick 方法,从而日期的计算便不会停顿。 这对于自动优化器的整个操作至关重要。

下一步是创建指向含有机器人类的指针,因为相较于全局范围,从 OnInit 方法充实它更便捷。 还有,创建一个存储互斥锁名称的变量

该机器人在 OnInit 方法中被实例化,且在 OnDeinit 中被删除。 为了确保回调机器人时传递到达的最新即时报价,在机器人指针处调用重载的 OnTick() 方法。 一旦此操作完毕之后,在 CRobot 类中写入机器人。

通过聚合,或是通过在全局范围内创建 CAutoUpLoader 实例来加载报告的变体是相似的。 若您有任何疑问,请随时与我联系。 

因此,通过使用此机器人模板,或将相应的调用添加到现有算法中,您可以协同自动优化器一起操纵它们,这将在下一篇文章中进行讨论。

结束语

在第一篇文章中,分析了 XML 报告文件的操作机制,和文件结构的创建。 在第二篇文章中则研究了报告的创建。 实验了报告生成机制,从加载历史记录对象开始,到生成报告对象结束。 在研究报表创建过程中涉及到对象时,详细分析了其计算部分。 本文还包含主要系数公式,以及可能的计算问题的说明。

正如本文概述中提到的那样,本部分中阐述的对象充当了数据加载机制,和报告生成机制之间的桥梁。 除了保存交易报告文件的功能之外,本文还包含参与 XML 报告卸载的类的说明,以及可以自动运用这些功能的机器人模板说明。 本文还论述了如何将创建的功能添加到现有算法。 这意味着自动优化器用户可以同时优化新旧算法。   

附件存档中有两个文件夹。 将它们都解压缩到 MQL/Include 目录。 必须将 ReportManager.dll 库文件添加到 MQL5/Libraries。 您可以从先前的文章下载。

附件中包含以下文件:

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

全部回复

0/140

量化课程

    移动端课程