首页 > 返佣资讯 > EA测试 > 以MQL5 编写的EA 交易程序的测试与...

以MQL5 编写的EA 交易程序的测试与优化指南

发布时间:

简介

大多数时候,当一名开发人员编写 EA 交易程序时,确保 EA 交易程序实现其优异的盈利目标始终是一个非常费力的过程。在本文中,我们将关注测试和优化 EA 交易程序,从而实现编写 EA 交易程序的目标所需的某些主要步骤。 

1. 识别和纠正代码错误

让我们先来看看在编写EA交易程序代码的过程中经常遇到的某些常见代码错误。大多数时间,初学者在编写他们的代码或者修改其他开发人员编写的代码时,都会经历识别和纠正代码错误的困难时期。在这一部分,我们将查看使用 MQL5 编辑器识别和纠正某些此类错误有多容易。

您刚刚完成代码的编写,并且它看起来应该能够运行,因为您几乎确信代码不含错误。或者,它是其他人写的代码,您进行了某些更改,并且当您单击Compile(编译)按钮(或按F7)时,系统向您显示代码中的一系列错误,如 MetaEditor 工具箱窗口的 Error(错误)选项卡所示。

哇!38 个错误和 1 个警告,您的代码可能没有如这里显示的这么多错误,我们想要查看的是在编译我们的代码时可能出现的各种类型的错误,以及我们如何解决这些错误。让我们对上图进行说明。

  • 标有 1 的部分显示 代码中错误的说明。这让我们了解错误看起来象什么。
  • 标有 的部分向我们显示 哪个文件出现错误。如果我们包含出错的文件,这非常重要。通过这个提示,我们能够知道要针对所述错误检查哪个文件。
  • 标有 3 的部分向我们显示错误在代码中的哪一行和哪一列(在行上)。这使我们能够知道针对所述错误检查具体的哪一行。
  • 标有 4 的部分显示 编码错误和警告的总数

现在,让我们开始逐个解决这些错误。让我们滚动到 Error(错误)选项卡的第一行,这样我们就能从头开始。

第一个问题描述如下:"truncation of constant value"(切断常数值),发现于第 16 行,第 20 列,要在我们的代码中定位到准确的行,从 MetaEditor 的 Edit(编辑)菜单,选择 Go to Line(前往行),或在键盘上按 CTRL+ G

将显示一个对话框。

对话框中显示的行号范围是代码的总行数。在本示例中 (1-354) 显示我们的代码包含 354 行。

在输入框中输入要检查的行号,然后单击 OK(确定)按钮。您将直接去到该代码行号。您将看到鼠标光标在该行中闪烁。

此处的问题是我们将 Lot 声明为一个整数 (int) 变量,但是用双精度值 (0.1) 对其进行初始化。为纠正此错误,我们将 int 改为 double,保存文件,然后再次单击 COMPILE(编译)按钮,以查看该问题是否得到解决。

在重新编译时,第一个问题已经得到解决,但是我们仍然有其他问题,如下所示:

现在,我们遵循以上相同步骤并转至第 31 行。但是,这次我们将在 Errors(错误)选项卡上 右键单击错误,然后选择 Go to line(转至行)

或者简单地选择错误,然后在键盘上按 Enter 键。您将被立即带到第 31 行代码。

您将在具体的第 31 行代码上看到鼠标光标闪烁,还有一个小的圆形红色按钮(错误图标)。

但是,如果这是一个警告消息,例如我们先前纠正的第 16 行上的第一个消息,它将显示一个黄色三角形按钮(警告图标):

显然,我们在第 31 行上没有任何问题,但是错误说明指出:"'STP' - unexpected token" (STP - 意外标记)。

则我们必须检查上一代码行(即第 30 行),以查看是否有错。通过细心检查,发现第 30 行在 "double ccminb = -95.0000" 之后缺少分号,这便是在第 31 行出错的原因。我们通过在 -95.0000 之后输入分号来解决此错误,然后重新编译。

现在,第 31 行的错误不见了。接下来是第 100 行,如下所示:

嗨!Olowsam,既然我们在每次纠正之后必须进行编译,为什么我们不一次性检查所有的行,在完成所有纠正之后才编译代码,而不是每次纠正之后都进行编译呢?

您问过这个问题吗?

从某种意义而言您可能是对的,但是我不建议这样做。问题始终是要一个接一个地解决 – 逐步。任何将问题堆在一起同时解决的试图都可能让人剧烈头痛。您很快就能理解原因——请耐心一点。

回到我们的问题,我们将针对下一错误检查第 100 行。错误指出:"'if' - expressions are not allowed on a global scope"(在全局范围内不允许 if 表达式)。我确信第 100 行中的 if 表达式并不在全局范围内,但是为什么我们遇到此错误呢?请让我们前往第 100 行。

我们在第 100 行中找不到任何问题,并且我们刚刚完成第 31 行的纠正,我们确信现在问题在第 32 行和第 99 行之间。因此让我们向上移动到第 99 行(我们有一个注释,因此它不可能是错误)。如果我们继续向上查看,直到声明(MqlTickMqlTradeRequest 和 MqlTradeResult),它们也是正确声明和使用标点符号的。

接下来,让我们看看这些声明代码行之前的 if 表达式的代码,并确定该表达式是否正确。经过非常仔细的研究,我们发现 if 表达式有右括号,但是没有左括号。

添加左括号,然后再次编译代码。

//--- 我们是否有足够用于处理的柱数
   int Mybars=Bars(_Symbol,_Period);
   if(Mybars<60) // 如果总柱数少于60
    {
      Alert("我们有的柱数不足60, EA 将要退出!!");
      return;
     }

编译代码之后,第 100、107、121、126、129 等行上的错误都彻底清除了,但是又显示新的错误。明白最好逐步解决错误的原因了吗?

接下来,我们移到有两个错误的第 56 行:"'cciVal1' - parameter conversion is not allowed"('cciVal1' - 不允许参数转换)和 "'cciVal1' - array is required"('cciVal1' - 需要数组)

通过仔细检查第 56 行,发现 cciVal1 被假定作为一个数组声明。难道是我们没有将其声明为数组但是现在要将其作为数组使用?让我们检查声明部分以确认,之后才能知道我们接下来该怎么做。

//--- 其他参数
int maHandle;               // 我们移动平均指标的句柄
int cciHandle1,cciHandle2;  // 我们CCI指标的句柄
double maVal[];             // 用于保持每个柱的移动平均值的动态数组
double cciVal1,cciVal2[];   // 用于保持每个柱CCI指标值的动态数组
double p1_close,p2_close;   // 用于分别保存柱1和柱2收盘价的变量

从这里,我们可以看到我们将 cciVal1 错误地声明为一个双精度数而不是一个动态数组,因为我们遗漏了方括号 ([])。让我们添加方括号(对 cciVal2[] 也是如此),然后编译代码。

//--- 其他参数
int maHandle;               // 我们移动平均指标的句柄
int cciHandle1,cciHandle2;  // 我们CCI指标的句柄
double maVal[];             // 用于保存每个柱的移动平均值的动态数组
double cciVal1[],cciVal2[]; // 用于保存每个柱CCI指标值的动态数组
double p1_close,p2_close;   // 用于分别保存柱1和柱2收盘价的变量

哇!好多错误都消失了。我们仅纠正了第 56 行中报告的错误,其他一些错误也自动解决了。这是因为第 56 行中报告的错误对其他错误也有影响。这是为什么最好遵循逐步过程解决代码错误的原因。

现在,我们将移到第 103 中报告的错误:"'GetLastError' - undeclared identifier"('GetLastError' - 未声明标识符)  请稍等,GetLastError 被假定为一个函数……让我们前往第 103 行以查看问题是什么。

//--- 使用 MQL5 MqlTick 结构读取最新报价
   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("读取最新报价出错 - 错误:",GetLastError,"!!");    // 103行
      return;
     }

问题确实是在第 103 行中。GetLastError 是一个函数,并且每一个函数需要一对圆括号以输入参数。让我们输入一对空的圆括号,然后编译代码。一对空的圆括号指出函数不采用自变量或参数。

//--- 使用 MQL5 MqlTick 结构读取最新报价
   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("读取最新报价出错 - 错误:",GetLastError(),"!!");  // 103行
      return;
     }

接下来,我们移到第 159 行:"'=' - l-value required"('=' - l-需要值) 和  一个警告:"expression is not Boolean"(表达式不是布尔类型)。让我们前往第 159 行并查看这个错误有何含义。

      else if(PositionGetInteger(POSITION_TYPE) = POSITION_TYPE_SELL) // 159行
       {
            Sell_opened = true; // 是卖出

可以在这里发现,我们在 if 语句中将 POSITION_TYPE_SELL 的值赋予  PositionGetInteger(POSITION_TYPE),并且这并不是我们的本意。我们本来是想进行比较的。现在我们将表达式改为使用等于运算符而不是使用赋值运算符。(即用 ‘==’ 代替 ‘=’)。纠正后编译代码。

      else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) // 159行
       {
            Sell_opened = true; // 是卖出

太好了!现在我们还有一个错误要解决。让我们前往第 292 行并查看为什么它说 "'PositionsTotal' - undeclared identifier"('PositionsTotal' - 未声明标识符)。请稍等,您能想起我们以前看到过类似的错误吗?‘GetlastError’ 第 103 行。可能我们也忘记向 PositionsTotal 添加一对圆括号了,因为它是一个函数。让我们前往第 292 行进行确认。

bool CheckTrade(string otyp)
{
   bool marker = false;
   for (int i=1; i<=PositionsTotal;i++)  // 292行
   {

正如我们所怀疑的,这是因为我们忘记向 PositionsTotal 函数添加一对圆括号了。现在,添加一对圆括号 (PositionsTotal()),然后编译代码。我同样要指出的是,如果我们确实使用一个没有在代码的任何地方声明的变量,我们也有可能遇到此错误。

太棒了!现在,我们已经能够纠正所有编译错误。是时候调试我们的代码并查看是否存在运行时错误了。在这里,我们将不会深入探讨如何调试我们的代码,因为它已经在本文中解释过了。

随着调试进程的开始,我们注意到另一个错误:

单击 OK(确定)按钮,您将被带往生成该错误的代码行。

如您从上图所见,错误是第 172 行中的代码生成的。因为错误是 "Array out of range"(数组超出范围)错误,这意味着我们预计要从数组获得的值超出可用的数组值范围。因此,我们将前往在其中将指标缓存复制到数组的行以查看问题是什么。

//--- 使用句柄把我们指标的新值复制到缓冲区(数组)中
   if(CopyBuffer(maHandle,0,0,3,maVal)<0)
     {
      Alert("复制 MA 指标缓冲区出错 - 错误:",GetLastError(),"!!");
      return;
     }
   if(CopyBuffer(cciHandle1,0,0,3,cciVal1)<0 || CopyBuffer(cciHandle2,0,0,3,cciVal2)<0)
     {
      Alert("复制 CCI 指标缓冲区出错 - 错误:",GetLastError());
      return;
     }

我们可以从 CopyBuffer 函数看到,我们仅复制了三个值(Bar 0、1 和 2),这意味着我们只能存取 maVal[0]maVal[1]、和 maVal[2] 以及 cciVal1[0]cciVal1[1]、和 cciVal1[2] 等的数组值。但是在我们的第 172 行代码中,我们试图获取 cciVal1[3] 的数组值。这是为什么生成错误的原因。现在,停止调试程序,从而让我们能够纠正错误:

为纠正此错误,我们需要增大要从指标缓存复制到 5 的记录的数量,从而让我们能够获得 cciVal1[0]、cciVal1[1]、cciVal1[2]、cciVal1[3],甚至 cciVal1[4](需要时)的数组值。

//--- 使用句柄把我们指标的新值复制到缓冲区(数组)中
   if(CopyBuffer(maHandle,0,0,5,maVal)<0)
     {
      Alert("复制MA指标缓冲区出错 - 错误:",GetLastError(),"!!");
      return;
     }
   if(CopyBuffer(cciHandle1,0,0,5,cciVal1)<0 || CopyBuffer(cciHandle2,0,0,5,cciVal2)<0)
     {
      Alert("复制CCI指标缓冲区出错 - 错误:",GetLastError());
      return;
     }

如下所示纠正代码,然后再次启动调试程序。这次,我们没有发现我们的 EA 交易程序执行交易操作时再次出现错误。

2. 测试 EA 交易程序

一旦我们确定我们的代码不含错误,则是时候测试 EA 交易程序是否能够获取带给我们最佳结果的最佳设置。为了展开测试,我们将使用策略测试程序,该程序内置于MetaTrader 客户端中。要启动策略测试程序,请前往客户端上的 View(视图)菜单,然后选择 Strategy Tester(策略测试程序)。

2.1. 我们的 EA 交易程序的初步测试

此时,我们希望使用 Market Window(市场窗口)中的可用交易品种测试 我们的 EA 交易程序。通过此结果,我们将能够猜测我们可以针对哪一货币交易对更好地优化我们的 EA 交易程序。确保 Market Window(市场窗口)包含您要针对其测试 EA 交易程序的大多数货币。

在策略测试程序的 Settings(设置)选项卡中选择 Expert(专家),然后是需要的时间段/时间框架(当然,您也可以针对不同的时间框架测试EA交易程序),然后在Optimization(优化)字段中选择 'All Symbols Selected in MARKET Watch'(在市场报价中选择的所有交易品种)。正前方是优化结果参数,选择 Balance + max Profit Factor(结余 + 最大盈利系数)。

1. 选择价格变动生成模式 –(Every Tick,每一价格变动)

2. 选择优化类型 –(All Symbols Selected in MARKET Watch,在市场报价中选择的所有交易品种)

3. 选择预期的优化结果类型

您可以从客户端的帮助文件中找到各种优化类型的详细信息。我们并不转发测试,因此保留 Forward(转发)为 No(否)。

对于此测试,将使用 Inputs(输入)选项卡中主要的值/参数(以绿色突出显示)。

一旦您完成,切换到 Settings(设置)选项卡,然后单击 Start(开始)按钮。在测试完成时,您将在 Journal(日志)选项卡中看到类似于以下内容的一条消息:

一旦完成测试,前往 Optimization Results(优化结果)选项卡查看结果。

我们的兴趣在于依据我们的设置 – Balance + max Profit Factor(结余 + 最大盈利系数)提供最大结果的交易品种。为此,让我们通过单击 Result(结果)标题对结果进行排序,从而让具有最大结果的交易品种列在顶部。

图 25. 初步优化结果分析

我们可以从此结果看到,我们的 EA 交易程序能够在我们选择的时间框架内对以下交易品种(EURUSD、EURJPY、AUDUSD)实现盈利。您可以针对其他时间框架进一步执行此测试,例如 30 分钟,看一看您得到什么结果。这是一个作业,并且请分享结果,从而让我们大家都能学习。

从我们的初步测试结果,现在我们决定针对哪些交易品种和时间框架来优化我们的 EA 交易程序。

在此示例中,我们将针对 EURUSD 和 1 小时时间框架优化我们的 EA 交易程序。以下因素促使我们做出如此选择:

  • 盈利系数:

盈利系数是该测试的总盈利与总损失之比。盈利系数越高,则您的交易策略就越赚钱。

  • 亏损百分比:

这指与最大资产净值相比资产净值的相对亏损或最大损失(以百分比表示)。亏损(以百分比表示)越少,策略越好。

  • 回收系数:

这是盈利与最大亏损之比。它反映交易策略的风险。

已经决定了要使用的交易品种和时间框架,现在是时候优化我们的 EA 交易程序了。

2.2. 优化 EA 交易程序

优化是通过使用确定我们在 EA 交易程序中编程的策略的效率或盈利能力的各种系数(参数)进行测试来细调我们的 EA 交易程序的性能的过程。它是类似于测试 EA 交易程序的过程,但不仅仅测试一次,视在 Input(输入)选项卡中选择的参数而定,要进行很多次的测试。

要开始,我们前往 settings(设置)选项卡并启用优化,然后选择我们希望从优化获得的结果的类型。

1. 选择价格变动生成模式 –(Every Tick,每一价格变动)

2. 选择优化类型 – (Fast Genetic Based Algorithm,快速遗传算法)

3. 选择优化的预期结果类型(在这里我们选择 Balance + Max Profit Factor(结余 + 最大盈利系数))

您可以从客户端的帮助文件中找到各种优化类型的详细信息。我们并不转发测试,因此保留 Forward(转发)为 No(否)。设置好优化属性之后,让我们在 Inputs(输入)选项卡中设置用于优化的参数。

因为我们是进行优化,我们将仅仅专注于以黄色突出显示的区域。首先,必须取消选中我们不想在优化中使用的任何参数。换言之,我们将仅选中我们希望在 EA 交易程序的优化中使用的参数。在这里,我已经选中了五个参数,但是视您的策略效率所依据的参数而定,您可以只选中一个参数或两个参数。例如,您可以仅选中 Moving Average(移动平均线)和 CCI 周期,此优化结果将让您知道向您的 EA 交易程序提供最佳性能的每个指标的最佳值。这是优化的精华所在。

选中参数的数量也将确定您的 EA 交易程序将经历的测试的总次数。您很快就会看到我所说的。

设置值

Start(起始):

这是用于选择用于优化的变量的起始值。让我们使用 Stop Loss(止损)变量来解释如何设置值。对于 Stop Loss(止损),我们已经要求测试程序从值 30 开始。这将是优化期间 Stop Loss(止损)使用的最小值。

Step(步长):

这是 Stop Loss(止损)的增加值。如果我们将增量设置为 2,则表示在第一次测试中,它使用 30 作为 Stop Loss(止损)的值,在第二次测试中它将使用 32、36、34 等作为 Stop Loss(止损)的值。但是这并不意味着它将使用 30,接着使用 3234 等。不是这样的,它随便选择一个值,但是这些值必须始终是在 Start(起始)值和 Stop(停止)值之间的 2 的倍数。

Stop(停止):

这将是用于优化的最大值。在这里,我们指定了 38。这意味着将用于测试的值将在 30 和 38 之间但必须是 2 的倍数。它将不会使用 40 或任何更大的值。

测试的执行总次数取决于这三个部分的设置。在我们的示例中,测试程序将 Stop Loss(止损)的总共 5 五种可能组合在一起,如 Inputs(输入)选项卡的 Steps(步长)列所示,它将包含 Take Profit(止盈)的总共 8 种可能,等等。如果您考虑所有其他变量,它将产生数以千计的可能性(测试/经历次数)。如果您不想为一个 EA 交易程序的优化等待太多时间,确保您不包含或不选中太多的变量;可能您的 EA 交易程序的性能真的只是依赖两个或三个变量而已(最特别地,指标周期,如果您在您自己的代码中使用它们的话)。您也必须确保您的步长值不会导致过多可能性(测试)。例如,如果我们使用 1 作为步长值,则我们将 Stop Loss(止损)的尝试次数增加到 10。如前所述,完成优化过程所需的总时间取决于您在您的系统中设置的可用代理的数量。

我相信解释已经足够。

一旦我们完成输入设置,我们就可以回到 Settings(设置)选项卡,然后单击 Start(开始)按钮。

一旦完成优化,我们就可以在 journal(日志)选项卡中查看详细信息。

要在每次测试进行或完成之后查看结果,我们前往 Optimization Results(优化结果)选项卡。按 Results(结果)对输出进行排序始终是一个好习惯,因为这样我们能够轻松地识别依据我们的优化设置向我们提供最佳结果的设置。单击 Optimization Result(优化结果)选项卡内的 Results(结果)将按升序或降序排列结果。

切换到 Optimization Graph(优化图)选项卡以查看优化图看起来象什么。

不理解您看到的?别担心;您看到的点是您的 EA 交易程序经历若干次测试的绘图,针对依据您选择的优化结果类型而得出的优化结果绘制。在我们的示例中,我们选择了 Balance + max Profit factor(结余 + 最大盈利系数)。

2.3. 解释结果

为了成功解释优化报告,请前往 Optimization Results(优化结果)选项卡。您将发现,您看不到某些字段,例如Profit factor(盈利系数)、Expected Payoff(预期收益)、Drawdown%(亏损百分比)等。要查看它们,在 Optimization Results 选项卡中右键单击任何地方,然后选择您要查看的附加信息

添加这些附加记录之后,我们将分析优化结果以决定我们的 EA 交易程序的最佳设置。

图 33. 分析优化结果

从上图,标有 A 和 B 的突出显示的部分指出我们的 EA 交易程序的最佳结果。现在,完全由您进行选择,这完全取决于您正在寻找什么。但是,在这里我们同时对提供最大利润和具有较低 drawdown%(亏损百分比)的设置感兴趣。

如您所能看到的,A 部分(以黄色突出显示)具有最好结果 (Balance + max Profit Factor22381.71,盈利为 924.10,而 B部分(以绿色突出显示)具有第二好结果 22159.25 和较高盈利 936.55A 部分具有较低的 Drawdown%(亏损百分比)1.78,而 B 部分具有较高的亏损百分比 1.95

策略测试程序将优化结果保存到"<客户端数据文件夹>\Tester\cache" 文件夹。在您的案例中,所有优化数据将被保存到 cci_ma_ea.EURUSD.H1.0.xml 文件。

文件名采用以下格式:ExpertName.SYMBOL.PERIOD.GenerationMode.xml,其中:

  • ExpertName - EA 交易程序的名称;
  • Symbol - 交易品种;
  • Period - 时间框架 (M1,H1,...)
  • GenerationMode - 价格变动生成模式(0-每一价格变动、1 - 一分钟 OHLC、2 - 仅开盘价)。

可以用 MS Excel 打开 XML 文件。

2.4. 选择最佳结果

为了最终获得最佳结果,我们需要再次查看优化图。切换回优化图。在图中右键单击任何地方,然后选择  1D Graph(单维图)。

通过此图,我们可以轻松地看到提供最佳结果的每个输入参数的值。现在,您可以开始选择各个参数以查看最佳值。右键单击该图,然后选择 X-Axis(X 轴),再选择您要检查的参数。它将看起来如下所示(针对止损)

图 35. 从优化结果获取最佳止损值。

实际上,从优化结果分析,显然最佳止损为 34,最佳获利为 78,最佳 CCI_Period1 为 62。要获得 MAPeriod 和 CCI_Period2 的最佳值,请按上述方法分别选择它们。

获得每个参数的最佳值之后,是时候对我们的 EA 交易程序进行最终测试了。

2.5. 最终测试

最终测试涉及将最佳参数组合在一起以测试 EA 交易程序。在这个示例中,我们将使用在策略测试程序的 INPUT(输入)部分发现的最佳值

在策略测试程序的 SETTINGS(设置)选项卡中,我们将禁用 Optimization(优化)

现在,我们将单击 START(开始)按钮以开始测试。一旦测试完成,我们将在 RESULTS(结果)选项卡获得结果

我们也在 GRAPH(图形)选项卡中获得测试的图形。

总结

在本文中,我们讨论了识别和纠正代码错误的方法,还讨论了如何测试和优化 EA 交易程序以从市场报价中获得最佳交易品种。

通过本文,我相信使用编辑器检查代码错误以及使用策略测试程序测试 EA 交易程序,使编写可盈利的更佳 EA 交易程序成为可能。

柚子返佣网提醒您,投资金融产品会有承担损失的风险,请理性投资。关注柚子返佣网,为您炒货币对、炒期货带来更多相关金融资讯、更高返佣比例、更简单的线上开户模式!

柚子返佣交易流程

  • 免费注册
  • 下载软件
  • 在线开户
  • 注入资金
  • 立即返佣